I have two components, a Parent and a Child component. In the Parent component, I have an array that is empty to start off with:
publicCrowsOnPerchValues: number[] = [];
I use this array to display data on a chart using chart.js
. I am using Firebase's RTDB to store and retrieve the data. Here is how the aforementioned array is populated in the Parent Component:
getCrowOnPerchDataChildren() {
//get a snapshot of the child added
this.$childCrowOnPerchSub = this.crowboxService
.getCrowOnPerchData()
.stateChanges();
this.$childCrowOnPerchSub
.subscribe(action => {
//set the showUserId to false as the user has already set up the crowbox
this.showUserId = false;
//get the index of the key from the date array
let indexOfKey = this.crowsOnPerchDate.indexOf(action.key);
//if the index is -1, then the date does not currently exist
//this means that it is a new date, so we push it onto the array
//since it is a new date, we also push on the value onto the value
//array
if (indexOfKey == -1) {
this.crowsOnPerchDate.push(action.key);
this.crownsOnPerchValues.push(action.payload.val().value);
} else {
//if it does exist, then we don't need to add the new date
//simply replace the existing data value with the new data value
//for the same date
this.crownsOnPerchValues[indexOfKey] = action.payload.val().value;
}
//reset the bar charts data as well as labels
this.crowOnPerchChartLabels = this.crowsOnPerchDate;
this.crowOnPerchChartData = [
{ data: this.crownsOnPerchValues, label: "Number Of Crows That Landed On The Perch" }
];
});
}
In the above function, I am essentially populating the array as the user adds data into the Firebase RTDB. It works off a subscription to the observable.
Now, I would like to pass this array to my Child Component.
In the Child Component, I set up an Input()
variable like so:
@Input() crowsOnPerch!:number[];
Then, in the parent.component.html, I pass the array like so:
<app-child [crowsOnPerch]="crownsOnPerchValues"></app-child>
This works, except, I receive the empty array. How might I get constantly receive the array as it updates? Should I use a shared service instead?
CodePudding user response:
Angular Detection Strategy works in a way, that with complex data types like array
or object
it doesn't check the content inside, it only checks the identity of these data types.
It means that if you want to have the changes reflected in your @Input
you shouldn't mutate the existing array, but rather create a new one (so the identity of your array will be changed).
You can do that using a spread operator:
// Instead of this:
this.crownsOnPerchValues.push(action.payload.val().value);
// Do this:
this.crownsOnPerchValues = [
...this.crownsOnPerchValues,
action.payload.val().value
]
// Instead of this:
this.crownsOnPerchValues[indexOfKey] = action.payload.val().value;
// Do this:
this.crownsOnPerchValues[indexOfKey] = action.payload.val().value;
this.crownsOnPerchValues = [...this.crownsOnPerchValues];
CodePudding user response:
The "reactive" way to do it is to make your array an Observable stream and then to wire up the child component to receive it as an Input. It should look something like this,
Parent component
*.ts
public myArray$: BehaviorSubject<number[]> = new BehaviorSubject<number[]>([]);
When your array changes, push that change to the Observable stream
this.myArray$.next([1, 2, 3]);
*.html
<child-component [myArrayInput]="myArray$ | async">
Child component
*.ts
@Input() myArray: number[];
I am using a BehaviorSubject to allow the use of the .next() function, but you could do this with a simple Observable as well.
The reason that it is not working the way you have it is Angular's change detection. It won't detect the changes inside of the array, only a change to the entire reference. You would have to create an entirely new array and assign it to the variable you are passing to the child component each time there was a change.