EI-01
EI-01

Reputation: 1095

How to apply @ViewChildren in angular2 for communication between Parent and child

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 {
}

Upvotes: 1

Views: 5010

Answers (2)

Brendan Bates
Brendan Bates

Reputation: 116

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.

Upvotes: 4

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 658253

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

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

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

Upvotes: 2

Related Questions