I have a parent and child component. In the parent I am searching for a user and give the userId via #Input to my child component. This is working.
After my child component is getting the value I want to call a function so that I am getting the card from the user back. How do I do this? I always getting a card is undefined error.
<p (show)="serachForCard()">
User: {{userId}}
Card: {{card.id}}
</p>
My Idea was to call a function but it is still not working.
Function in my ts file:
serachForCard() {
this.cardService.getCardByUserId(this.userId)
.subscribe(
(data: Card) => this.card= data
)
}
Implement OnChanges interface inside your child component and call your function from ngOnChanges method.
#Component({
...
})
export class MyComponent implements OnChanges {
#Input() someInput: string;
ngOnChanges(): void {
// do something with this.someInput
}
}
Documentation: OnChanges
A lifecycle hook that is called when any data-bound property of a directive changes. Define an ngOnChanges() method to handle the changes.
Related
I have a parent component ValidateSessionComponentwhich has a child component LoginFormComponent. In my unit test, I want to emit a value from LoginFormComponent but I am unable to figure out how I can do so.
The HTML of the ValidateSessionComponent has reference of the LoginFormComponent.
<app-login-form #loginForm (formOutputEvent)="handleFormValues($event)" [userId]="username"></app-login-form>
The LoginFormComponent looks like
export class LoginFormComponent implements OnInit {
loginForm:FormGroup;
formData:LoginFormValues;
#Input() userId;
#Output() formOutputEvent: EventEmitter<LoginFormValues>;
existingUser:UserSigninInfo;
..
}
In the test, i want to emit formOutputEvent by calling formOutputEvent.emit(formData); But I can't figure out how to access formOutputEvent of LoginFormComponent in ValidateSessionComponent's spec.
fit('should send signin request on receiving form values ',(done)=>{
//let loginComponent:LoginFormComponent = TestBed.get(LoginFormComponent); //IF I UNCOMMENT THIS THEN I GET ERROR NO PROVIDER OF LOGINFORMCOMPONENT
let userService:UserManagementService = TestBed.get(UserManagementService);
component.loginForm.formOutputEvent.emit(new LoginFormValues('test#test.com','somepassword'));
spyOn(userService,'signinUser');
setTimeout(()=>{
expect(userService.signinUser).toHaveBeenCalledWith(new UserSigninInfo('test#test.com','somepassword'));
done();
},1000);
});
I thought I could use the ViewChild directive #ViewChild(loginForm) loginFormRef; but I suppose I'll get an ElementRef. I can't figure out how to access the formOutputEvent.
Scenario: I have a list of companies that each have an array of projects as one of their variables. I will display the list of companies in the parent component/html, and only when clicking on their corresponding 'open' does a child component open to display the list of projects for that company. This list is a FormArray that is editable.
I created this FormArray example as the standalone projects component to interact and perform CRUD operations with example data.
My goal now is to open the form as a child component when I click the 'open' button on each individual company as in THIS stackblitz.
In the example it appears that this.setData(); within the constructor is causing the upset.
I have found through experimentation that, by commenting this line out causes the app not to crash, but of course the FormArray will not be loaded when I click the company 'open' button. However, I have also found that writing {{company.name}} in the child component DOES output the company details in the child, so it shows data is going through correctly.
I just cannot understand what is going wrong?
Try ngDoCheck() lifecyle hook
A lifecycle hook that invokes a custom change-detection function for a
directive, in addition to the check performed by the default
change-detector.
ngDoCheck() {
this.setData();
}
The problem lies in this.setData() call inside SubForm class constructor.
You never checked for null/undefined data. So, initially when the component loads, this.company is undefined. Hence, name variable will be assigned undefined value.
So, when program tries to access the value of this.company in the below line, it gives error:
setData() {
let control = <FormArray>this.myForm.controls.data;
control.push(this.fb.group({
name: this.company.name,
...
}));
}
Solution:
Add null/undefined check for this.company before calling this.setData():
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
data: this.fb.array([])
})
if(!isNullOrUndefined(this.company)){
this.setData();
}
console.log(this.company)
}
As I tried on the StackBlitz, few changes to be done to get it working:
You have to access the #Input variable in ngOnInit() by implementing an OnInit interface.
Parent Component .TS file:
isOpened : boolean = false; // one local variable of type boolean to open child compo
openInfo(company) {
this.isOpened = !this.isOpened;
this.openCompany = company;
this.open=true;
}
Parent Component HTML Code:
<mat-card *ngFor="let data of DATA">
{{data.name}}
<button mat-raised-button (click)="openInfo(data)">open</button>
</mat-card>
<div *ngIf="isOpened">
<subform [company]="openCompany"></subform>
</div>
Child Component .TS Code:
Import this:
import {
Component, OnInit, Input
} from '#angular/core';
and Component class:
export class SubForm implements OnInit {
#Input() company: any;
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
data: this.fb.array([])
})
console.log(this.company);
this.setData();
}
}
A working example
I am trying to pass data from parent component to child components, but I am getting data is undefined,
In my below code
parent component
here part_data I have declared in service
this._PartService.GetDataValues(this.search.item, this.search.filter_type, release_part).subscribe(
result => {
this.resultData = result;
this._PartService.part_data = this.resultData.data
})
child component
Here I am using observer
this.searchService.observer.subscribe(search => {
this.part = search.item;
this.specification = this._PartService.part_data.partMasterDataCompleteList[0];
})
Here I am getting PartMasterDataCompleteList[0] is undefined
Use this as the child component:-
#Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit {
#Input() hero: Hero;
constructor() {}
ngOnInit() {}
}
When you need to feed data to the component, use the below code:-
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
The hero property is #Input which receives data from the outer environment.
For more information, refer to Angular tutorial:-
https://angular.io/tutorial/toh-pt3
You can pass your data in sub component and get using #Input method.
There is one other way to pass data from one component to other component.
Click here
I am trying to implement a custom two way binding between two of my components.
I did read about the naming convention saying that you have to define a #Input() like "test" and then define a #Output() named "testChange".
I couldn't find anything about whether this is still up-to-date or not and I can't get my binding to work.
Some code within parentComponent:
<my-comp [(userGroupIds)]="userGroups"></my-comp>
MyComponent (child):
export class MyComponent implements OnInit, OnDestroy{
#Input() userGroupIds: number[];
#Output() userGroupIdsChange = new EventEmitter<number[]>();
updateValues(){
//here I am iterating through the rows of a table within my component
this.userGroupIds = this.tableRows.map(item => {return item['id']});
this.userGroupdIdsChange.emit(this.userGroupIds);
}
}
parentComponent:
export class parentComponent implements OnInit, OnChanges, OnDestry{
userGroups: number[];
constructor(){
this.userGroups = [];
}
ngOnChanges(changes: SimpleChanges){
if(changes['userGroups']){
// this is never show, ngOnChanges doesn't get fired;
console.log(this.userGroups);
}
}
}
Is there something I am missing? Did the Angular-Team change anything?
Afaik the binding does something like
[userGroupsIds]="userGroups" (userGroupsIdsChange)="userGroupIds=$event"
So I tried setting this myself, but no update either. Only thing that work was passing a function to the eventEmitter.
Your binding works like a charm, it does not trigger the ngOnchanges method, which is the expected behavior.
from the Angular docs :
OnChanges
Lifecycle hook that is called when any data-bound property of a directive changes.
as userGroups is not an #Input() it cannot be a "data-bound property" , its value changing internally will not run the ngOnChanges hook.
As i having parent and child component as follows,
Child Component (RWTaxComponent)
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'rw-tax',
templateUrl: 'rw.tax.component.html'
})
export class RWTaxComponent implements OnInit {
#Input() hsn: string = '';
#Input() srno: string = '';
#Input() taxPercent: number;
#Output() taxPercentChange: any = new EventEmitter();
constructor(
) { }
ngOnInit() {
}
ngOnChanges(event) {
console.log('HSN:: '+this.hsn);
console.log('SRNO:: '+this.srno);
if (this.hsn && this.srno) {
// Doing my logic here to find taxPercent
this.taxPercentChange.emit(this.taxPercent);
}
}}
Child component template (rw.tax.component.html) is,
<p>{{taxPercent | number:'1.2-2'}}</p>
And i invoked the above child component in my parent component as follows,
<rw-tax [(hsn)]="lineItem.hsn" [(srno)]="lineItem.srno" [(taxPercent)]="lineItem.taxPercent"></rw-tax>
I want to change taxpercent whenever hsn/srno is changed, these hsn/srno is not changed in RWTaxComponent itself, it is changed by parent component.
So i used ngOnChanges() event to detect that hsn/srno is changed and it getting invoked when ever i change hsn and srno in my parent component.
But the problem is after doing my logic to find taxpercent am trying to update the value in parent component by this.taxPercentChange.emit(this.taxPercent); and getting error "Expression has changed after it was checked"
Am i wrongly understood the Angular2 lifecycle ?
Please suggest a right way to do it...
This issue occurs when there is a change in input of the component and on that change if we are trying to change or emit any property.
Solution 1:
try promises resolve the changes and in then block emit the output event.
Solution 2 : (may not work)
Try setTimeout method on emitting the value