Reputation: 1095
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
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.
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.
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.
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
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