1

i am trying to understand @ViewChildren() decorator. I created a Component called Component-A that accepts a user email address. Created the parent component called "component-B" that has twins( 2 Component-A's). Now i found online tutorials on how parent and child components can interract by incorporating Event and property binding by using @Input() and @Output() decorator. But how can one achieve the same result using @ViewChildren() where we check if both emails entered are the same and print out they match?

@Component({
    selector: 'component-A'
    styleUrls: [],
    templateUrl: `<form>
                   <div>
                     <fieldset>
                     <legend>Component A</legend>
                       <div class="form-horizontal">
                          <div class="form-group">
                            <label for="inputEmail3" class="col-sm-2 control-label">Email</label>
                             <div class="col-sm-10">
      <input type="email" class="form-control" id="inputEmail3" placeholder="Email">
    </div>
</div>
</fieldset>
})

  export class ComponentA {

  }

Now i have a componentB:

@Component({
    selector: 'component-B',
    styleUrls: [],
    templateUrl: `
                  <form>
                    <div>
                      <fieldset>
                        <legend>Parent Component</legend>
                          <div class="form-horizontal">
                            <div class="form-group">
                              <label for="1" class="col-sm-2 control-label">1</label>
                               <div class="col-sm-10">
                                   <component-A></component-A>
                               </div>

                              <div class="form-group">
                              <label for="2" class="col-sm-2 control-label">2</label>
                               <div class="col-sm-10">
                                   <component-A></component-A>
                               </div>
                         </div>
                     </div>
               </fieldset>
            </div>
         </form>
})

   export class Parent {
}
EI-01
  • 1,075
  • 3
  • 24
  • 42

2 Answers2

4

I would like to try my shot at this answer, as I've been through this recently and it's taken quite a bit of research to fully understand. The change detection flow in Angular2 is very powerful but also very unintuitive. I'm going to answer your question and then go into a bit of detail.

@ViewChildren and AfterViewChecked

As the previous answer had specified, you can use the @ViewChildren decorator. I'm going to throw some extra code on top:

@Component({
    template: `
       <my-component #component *ngFor="let component of components"></my-component>
    `
})
export class Parent implements AfterViewChecked {
  private different: boolean = false;
  private components: any[] = [];
  @ViewChildren("component") componentList: QueryList<MyComponent>;

  // Inject the change detector for later use.
  constructor(private detector: ChangeDetectorRef) {

  }

  // Use "AfterViewChecked" hook to check for differences and apply them.
  // This will run after every change detection iteration.
  ngAfterViewChecked() {
    // Figure out if the emails are different.
    var email: string;
    var different = false;
    this.componentList.forEach(component => {
      if (email == undefined) {
        email = component.email;
      }

      if (email != component.email) {
        different = true;
      }
    });

    // If something changed, save it.
    if (different != this.different) {
      this.different = different;

      // MUST NOTIFY CHANGE DETECTOR.
      // This will tell angular to run change detection in the current scope.
      //
      // Note that we only run it when changes are detected, otherwise it will
      // cause an endless loop.
      this.detector.detectChanges();
    }
  }
}

Whew! That's a lot of code for a simple comparison. Basically, it uses the AfterViewChecked event to iterate through the children, checks to see if all emails match. If the difference state changes, it then tells Angular to perform a round of change detection.

What Does Angular2 Really Want?

As you wrote, the Angular2 framework heavily encourages @Input and @Output to transact data changes between components:

Now i found online tutorials on how parent and child components can interract by incorporating Event and property binding by using @Input() and @Output() decorator.

For example, you could use *ngFor on elements and passing data using Inputs and Outputs. Then, you wouldn't need to use @ViewChildren at all.

<my-component 
    *ngFor="let component of components"
    (emailChanged)="emailChanged(email)">
</my-component>

Using @Input and @Output fully encapsulates Angular2 change detection so that all changes between parent and children are detected within a normal change detection cycle. You don't need to do anything.

Efficient Change Tracking

Basically, the reason for this behavior is for efficient change tracking. If you have developed in Angular.js (version 1), you may know that change tracking was a mess. There was not a good system to know when changes had occured. You had to trust that people were calling the horrid scope.apply(), which would (if called on root scope) recompile the entire DOM. This is what often led to bad performance.

Angular2 has automated change tracking a decent amount to perform it when certain events occur. Hovers, clicks, HTTP requests, etc. They were clever however, and make it so that changes occur in zones within the application. This means that change detection happens in smaller, encapsulated units and not always on the entire application.

The Other Way

If you have data that you can't use @Input and @Output with (e.g. a dynamic list or some other reason) then you must use ViewChildren. If you modify the parent state based on the change detection of the child, then you must let the change detector know that things have changed. Why? Because most of the time, a child change detection round will not line up with the parent change detection round. When this happens you will get the following error message:

angular2.min.js:17 EXCEPTION: Expression 'isLoading in HostApp@0:0' has changed after it was checked. Previous value: 'false'. Current value: 'true' in [isLoading in HostApp@0:0]

What this is basically saying is that "hey, data changed outside of my change detection cycle". The resolution is to either fix it so that it changes inside of a proper cycle, or call detectChanges() after the work is done.

More can be read about this here at the Angular advanced documentation site.

2
export class Parent {
  @ViewChild(ComponentA) componentA:ComponentA;

  ngAfterViewInit() {
    console.log(this.componentA);
  }
}

For more details see https://stackoverflow.com/a/35209681/217408

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • it seems ViewChild only takes one child. But in parent B it accounts for multiple children as i have 2 components. But how do i access either one? – EI-01 Oct 07 '16 at 07:16
  • You can use `@ViewChildren(ComponentA)` instead. See also http://stackoverflow.com/questions/32693061/angular-2-typescript-get-hold-of-an-element-in-the-template/35209681#35209681 – Günter Zöchbauer Oct 07 '16 at 07:18
  • ok. I have another question if i were to use Output decorator i can emit the event and see updates from 2 way binding. But how i can achieve this using ViewChildren decorator – EI-01 Oct 07 '16 at 08:37
  • Sorry, but I don't understand. I can't see how Output and ViewChildren might be related. Would you mind creating a new question that demonstrates what you try to accomplish (with the code you currently have? – Günter Zöchbauer Oct 07 '16 at 08:40
  • sorry i did not mean that they are related. What i meant was ViewChildren seems to be another way of which a parent component can interact with its child. – EI-01 Oct 07 '16 at 09:15
  • i was asking if i wanted to bind the user input field from the child component to a variable in the parent component, how can that be achieved? – EI-01 Oct 07 '16 at 09:17
  • Input/Output are for data binding. ViewChild, ContentChild, ViewChildren, ContentChildren are to references to elements, components and directives to interact with them imperatively. You can subscribe to an output if you have a component reference. `childElement.someOutput.subscribe(...)` – Günter Zöchbauer Oct 07 '16 at 09:18
  • ``. In the parent component use ´ – Günter Zöchbauer Oct 07 '16 at 09:20
  • thank you for the response. As i see there are several ways components can interact with each other. From those 2 solutions above, which way is better approach – EI-01 Oct 07 '16 at 09:24
  • Use ViewChild only when necessary. If you can go with plain binding, use that instead. – Günter Zöchbauer Oct 07 '16 at 09:25
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/125156/discussion-between-user3497437-and-gunter-zochbauer). – EI-01 Oct 07 '16 at 09:27