Reputation: 1672
Passing a parameter to a child component in the parent component:
<app-conf-name
[inputName]='person.name'
(updatedNameEvent)='updateName($event)'>
</app-conf-name>
Defined in the component TS file
@Input() inputName: Names;
@Output() updatedNameEvent: EventEmitter<Names> = new EventEmitter();
constructor() { }
editName: Names = new Names();
ngOnInit() {
this.editName = this.inputName;
}
In the class, the Names type sets a default value for the correctPerson field.
export class Names {
indivId: number;
firstName: string;
prefName: string;
lastName: string;
suffix: string;
updated: boolean = false;
correctPerson: boolean = false;
correctAsIs: boolean = false;
correctionDate: Date;
addedDate: Date;
}
Why am I getting this error:
ERROR TypeError: Cannot read property 'correctPerson' of undefined
at Object.View_ConfNameComponent_0._co [as updateDirectives] (ConfNameComponent.html:9)
at Object.debugUpdateDirectives [as updateDirectives] (core.es5.js:13056)
at checkAndUpdateView (core.es5.js:12236)
at callViewAction (core.es5.js:12601)
at execComponentViewsAction (core.es5.js:12533)
at checkAndUpdateView (core.es5.js:12242)
at callViewAction (core.es5.js:12601)
at execComponentViewsAction (core.es5.js:12533)
at checkAndUpdateView (core.es5.js:12242)
at callViewAction (core.es5.js:12601)
The Object person
is initialized from a service that is called in the parent component.
Parent Component
person: Person = new Person();
constructor(private peopleService: PeopleService) { }
state: string = 'one';
ngOnInit() {
this.peopleService.getPersonById(this.Id).subscribe((data)=>{
this.person = data;
console.log(this.person);
this.person.name.correctPerson = false;
this.person.name.correctAsIs = false;
this.person.email.correctAsIs = false;
this.person.phone.correctAsIs = false;
this.person.address.correctAsIs = false;
});
}
Note that I've initialized the property that's throwing the error a second time via an assignment.
the original code for the child component used the input variable as the working variable.
After Several attempts to answer this question and get the full project working, I've gotten to a point where the final child component is the last one throwing errors after putting the ngIf's everywhere.
here's a link to the full GitHub Repo
Upvotes: 1
Views: 2223
Reputation: 3724
The problem with the above code is that the call to the server is asynchronous. This means that at the time the child component is created it does not necessarily mean that the name is defined.
The flow of data is as follows
There are two fixes to your problem. The first is by adding ngIf on your child component tag as below
<app-conf-name
*ngIf='person.name' [inputName]='person.name'
(updatedNameEvent)='updateName($event)'>
</app-conf-name>
The second solution, is to make use of a resolve guard, which resolves the data from your service, before the parent component is created.
In your route definition
{
path: 'your-path', component: YourParentComponent,
resolve: {
person: PersonResolveGuard
}
}
Your resolve guard
@Injectable()
export class BlockingResolveGuard implements Resolve<Person> {
constructor(private peopleService: PeopleService) {}
resolve(route: ActivatedRouteSnapshot):Observable<Person> {
// Call to service
}
}
In your parent component, inject the current activated route
constructor(private route:ActivatedRoute, private peopleService: PeopleService) { }
and in your ngOnInit()
ngOnInit() {
this.route.data.resolve((data:{person:Person}) => {
this.person = data.person;
}
}
This angular doc: gives a detail explanation how resolve guards (and other guards work) https://angular.io/guide/router#resolve-guard
Note that with the above solution, you need to resolve the id of the person you're retrieving the data for from the route parameters using the activatedRouterSnapshot in the resolve guard.
Upvotes: 0
Reputation: 54751
You are doing two things here. Lazy loading data and also loading that data in the parent ngOnInit
. Therefore, you have to assume it's possible for the @Input()
binding in the child to be undefined
.
This is because the @ViewChild
properties are set before ngOnInit
is executed, but @ContentChild
are set afterwards. This means that the template for the parent component is rendered to a view before your data is loaded, and that template is passing undefined
to the @Input()
binding on the child.
Note: I hope the above is accurate. Haven't had my morning coffee yet.
Either way, you should always support missing bindings for components.
@Input() inputName: Names;
@Output() updatedNameEvent: EventEmitter<Names> = new EventEmitter();
constructor() { }
editName: Names;
ngOnInit() {
this.updateEditName();
}
ngOnChanges(changes: SimpleChanges) {
if('inputName' in changes) {
this.updateEditName();
}
}
private updateEditName() {
this.editName = this.inputName || new Names();
}
Upvotes: 1
Reputation: 15211
You should not access @Input()
parameters in OnInit
but in OnChanges
instead. Its an Observable
and there is no guarantee it will resolve till the moment OnInit
fires.
For details, take a look here: Angular 2/4 @Input only moves intrinsics - not lists or objects
Upvotes: 3