I have the following Stackblitz.
When I am updating the child data source it's not rendering completely, the below method is not being called,
updatedSelectedText() {
this.SelectedData = "";
console.log(this.data);
this.data.forEach(el => {
if (el.Selected) this.SelectedData += el.Text;
});
}
I can make it call on
ngDoCheck() {
// this.updatedSelectedText();
}
But in real project, I have much complex logic written, that I do not want to call on ngDoCheck method.
Is there any way to force the child component to render completely
I tried ChangeDetectionStrategy but this is not working.
In Child2Component the updatedSelectedText method is called in ngOnInit which is called only once - when component is initialized. There is two ways to solve it:
by using a setter/getter for #Input()
by using a NgOnChanges lifecycle hook
NOTE: Both setter and ngOnChanges are called before ngOnInit so you can avoid this hook
In AppComponent the update method should update the jsondata object instead change single property
Take a look on Stackblitz
Why not pass the "child2" to onEmitEvent and call to the function updatedSelectedText()?
That's, you change your app.component.html
<!--see that use a template reference variable "#child2", and pass as
argument to update function -->
<app-child1 (onEmit)="update($event,child2)" [data]="jsondata"></app-child1>
<app-child2 #child2 [data]="jsondata.AdDimensionsMaster"></app-child2>
In update function of app.component.ts just call the function
//get the "child2" as "any"
update(event: any,child2:any) {
this.jsondata.AdDimensionsMaster[event].Selected = true;
this.jsondata.isactive = true;
child2.updatedSelectedText() //<--call the function
}
Related
I've got three components in my vue 3 app (using typescript and the composition api)
I'm reading a string value in the first component passing it throught a modal (2nd component) to a 3rd one.
Now the property gets to the 2nd component without an issue and I can read it there use it in a ref or whatever.
When passing the prop further to the 3rd component the watcher for that prop is not reacting to changes.
When I print the prop via another watcher I can see that its value is changing as I intended but the watcher does not react at all
Watcher code looks like this and I'm just passing props through the other components normally but this watcher is not reacting to changes.
const { myprop } = toRefs(props);
watch(myprop, (val: string) => {
output.value = val;
});
Thanks in advance for your time and help :)
Try to refactor your watcher like the follow example.
watch(() => props.myprop, (val: string) => {
output.value = val;
}, { immediate: true });
I think your watcher doesnt recognize the changes because myprops hold a reference from the parent component.
Im not quit sure, i need more context to understand.
I have a problem with ngOnChanges not being fired for a child component after data is being manipulated in parent via subscribe method.
Basically my main component looks like this:
public imageGroups: IImageGroup[];
public status$: Observable<ModelStatusesModel>;
public ngOnChanges(changes: SimpleChanges): void {
if (Boolean(this.treeData)) {
this.imageGroups = this.treeDataService.convertImageGroups(this.treeData);
this.status$ = this.updateStatus();
this.status$.subscribe((status: ModelStatusesModel) => {
this.treeDataService.updateStatus(this.imageGroups, status); // this function makes changes to this.imageGroups
console.log('subscribe happened');
});
}
}
HTML:
...
<ul class="treeview-nodes-wrapper">
<treeview-branch *ngFor="let group of (imageGroups)"
[group]="group"></treeview-branch>
</ul>
...
The branch has also ngOnChnages:
public ngOnChanges(): void {
this._currentNodeDisabled = this.group.isDisabled;
console.log(this.group); //returns isDisables as true
console.log(this.group.isDisabled); //returns false
console.log(this._currentNodeDisabled); //returns false
}
When I run my application this is the result in the console:
{ ... isDisabled: true ...},
false
false
subscribe happened
I was also trying to surround the call inside my subscription in a ngZone.run but without any success. Do you have any idea how to tell angular that ngOnChanges in the child triggered?
EDIT: What works for me is creating a property in the parent component (public change = false;) then toggling this property inside my subscribe method and giving it as an input to my children elements. That gets picked up as a change. Even though this solves my problem, it looks more like a very hacky way of writing the code.
This is a result of ngOnChanges being triggered when an input value changes, and with objects that are passed by reference the modification of their properties does not explicitly change the thing being passed down. The solution is often times to do a clone, ...spread operator, or reassign to the property that is being passed down in the parent's [input].
Your solution of having an additional input that changes to a new value to trigger ngOnChanges works as well, it's a bit of a workaround but that's web dev. just remember if you set the property to true then true again it won't trigger, so a count variable may work better (it's kinda hacky).
Doing a clone via JSON.parse solved my problem in a cleaner way than toggling a variable :
...
this.status$.subscribe((status: ModelStatusesModel) => {
this.treeDataService.updateStatus(this.imageGroups, status);
triggerChange();
});
...
private triggerChange() {
this.imageGroups = JSON.parse(JSON.stringify(this.imageGroups));
}
parent.component.html
<parent-comp [data]="mydata"> </parent-comp>
parent.component.ts
this.service.abc$
.takeUntil(this.ngUnsubscribe.asObservable())
.subscribe((data: myType[]) => {
this.mydata= data;
});
child.component.ts
#Input data;
Under Class I have below code
public ngOnChanges() { if (this.data) { console.log(this.data); } }
Now I want whenever I receive latest data in #Input data from Parent Component to child then my ngOnChanges function should trigger and print data in console.
But unfortunately ngOnChanges function does not trigger again. It trigger only once when component initialize
Please let me know if anyone wants more detail on same!
Thanks!
Given the lack of further information, I'd make an informed guess that #Input data is either an array or an object.
According to docs, ngOnChanges is:
A lifecycle hook that is called when any data-bound property of a
directive changes.
What it doesn't say however is how the property should be changed. Generally speaking, the hook is only triggered when the reference to the property is changed.
Consider the following eg.
Parent component controller
mydata = [];
updateMyData(value: any) {
this.mydata.push(value);
}
Parent component template
<app-child [data]="mydata"></app-child>
Child component controller
#Input() data: any;
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
}
Now you'd expect the ngOnChanges will be triggered every time the updateMyData() function is called in the parent component. But the reference to the variable mydata is never changed. So the hook won't be triggered. There are multiple ways to force the change detector to trigger the hook.
Method 1:
Bind the #Input decorator to a setter instead of the variable directly. Discussed in an answer already.
Method 2:
Use spread syntax to re-assign the variable in the parent component.
Parent component controller
mydata = [];
updateMyData(value: any) {
this.mydata = [...this.mydata, value];
}
You could use the same methods for objects as well.
Thank you everyone for your quick and effective solutions.
I got solution and it is not exactly but similar to what you guys suggested.
In the Parent Component:
**Earlier I was assigning this way**
`this.mydata= data;`
**But now I am assigning in below way:**
`this.mydata= cloneDeep(data);`
Note : cloneDeep is imported from lodash
It could be the data you are passing down. If it doesn't change then the ngOnChanges won't register any changes. Here's an example, you can see if a property changes multiple times then it will only trigger on the first update, but if you recreate the object it changes every time.
(see console logs in stackblitz)
https://stackblitz.com/edit/angular-ivy-qmb35h?file=src%2Fapp%2Fapp.component.html
You can do as I did and recreate the object each time to bypass this, or a more hacky way may be to keep a dummy 'count' variable that you pass down as well, and increment it each time you want the child component to register the change.
NgOnChanges will only be triggered for an input-bound property change of primitive type. That is because the reference to the data-variable has to be changed in order for the change to be registered, so that you can get it in this life-cycle hook.
So, the possible way you could achieve this is by changing the reference of 'mydata' variable. Like, assigning a new reference to the mydata variable when it is changed, mydata = [...mydata] if it is an array, or mydata = {...mydata} if it is an object from the parent component.
you can use setter and getter methods for #Input in angular.
Please refer the below lines for reference:
private _data: any;
#Input()
set data(data) {
this._data = data;
console.log(this._data);
};
From setter method only you can call any other method as well and can run any logic you want from there.
What is the point of this?
In the next example i found in book code we have a funtion in the component that changes component state createTimer()
createTimer = (timer) =>
{
const t = helpers.newTimer(timer);
this.setState({
timers: this.state.timers.concat(t),
});
client.createTimer(t);
};
It is wrapped:
handleCreateFormSubmit = (timer) => {
this.createTimer(timer); };
And passed down as property:
<ToggleableTimerForm
onFormSubmit={this.handleCreateFormSubmit}
/>
If you just do this:
<ToggleableTimerForm onFormSubmit={this.createTimer}/>
...and createTimer is a regular method of your class:
class YourComponent extends Component {
createTimer(timer) {
const t = helpers.newTimer(timer);
this.setState({
timers: this.state.timers.concat(t),
});
client.createTimer(t);
}
}
...then the issue would be that when the child component calls onFormSubmit, this wouldn't be set correctly.
But since you're setting a property of your instance and are using an arrow function:
class YourComponent extends Component {
createTimer = (timer) => {
const t = helpers.newTimer(timer);
this.setState({
timers: this.state.timers.concat(t),
});
client.createTimer(t);
};
}
...you don't have to worry about this being bound correctly, so you're right that you don't need the wrapper to fix that. Perhaps the wrapping function is there as a precautionary measure since the class method pattern is more common.
The only benefit you'd gain is if the child calls this.props.onFormSubmit with additional arguments that you want to ignore. If that's not the case, then you can leave out the wrapping function.
Generally you pass a function down that's bound to it's original component. This allows child components to alter the state of their parent. Imagine this Scenario :
I have a parent component with state property A. I have a function that takes an input and updates the state of the PARENT!!!!
I pass that as a prop to a child (maybe a form). When I submit the form, I call the function passed as a prop to update the PARENTS state with my form values.
A few things to keep in mind, lexical arrow functions LACK SCOPE, and if the function leverages the state of the component it must be bound to the component.
One problem I see in your code....
handleCreateFormSubmit requires a parameter. onFormSubmit will pas it one, but I don't think it'll be the one you're expecting. It'll pass the event. You can do something along these lines "
onFormSubmit={() => this.handleCreateFormSubmit(someValue)}
I'm new to VueJS and I'm trying to call a method inside a child component after my parent component changed the child's props.
I have the following in my child component:
methods: {
isChecked() {
if (this.cookiesTags) {
return this.cookiesTags.includes(this.tag.id);
}
return false;
}
},
updated() {
this.isChecked();
}
However, I get an error saying the isChecked() method is not a function, and I really want to avoid repeating the same code in both the methods and updated() hook.
Am I in the wrong way here? If someone could guide me to a better way of doing this it would be great also. I need to have reactivity in the child's props because the parent component is constantly changing this property via ajax, that's why I used the updated() hook here, but maybe there's a better way.
Thanks everyone!