Angular parent to child binding change detection - javascript

In a nutshell, I have a component which works as a text input. The parent component passes data into this component using #Input bindings. When the change event gets fired via the #Output binding, I perform some automatic validation in the parent component. This is to remove erroneous values and replace them with some sensible default for a better user experience.
Here is a basic version of the parent component
#Component({
selector: 'parent',
template: `
<div>
<child [value]="item.key1"
(valueChange)="onValueChange($event)">
</child>
</div>
`
})
export class Parent {
item = {
key1: 'test value',
key2: 'another test value'
};
onValueChange(newValue) {
// Perform some validation and set default
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
this.item.key1 = newValue;
}
}
And the child component
#Component({
selector: 'child',
template: `
<div>
<input type="text"
[(ngModel)]="value"
(blur)="onBlur($event)" />
</div>
`
})
export class Child {
#Input() value: string;
#Output() valueChange = new EventEmitter();
onBlur() {
this.valueChange.emit(this.value);
}
}
See here for a Plunker example
The issue I am having is as follows:
When entering a value in the child input and firing the blur event, the new value is bubbled up to the parent and the validation is applied - if the validation causes the value to get modified, it bubbles back down to the child's value correctly - happy days.
However, if the first 4 characters stay the same, and you append additional characters, upon blur the validation will still be applied and the parent's value will get updated correctly, but the child will preserve the "invalid" value - no more happy days.
So it looks to me like Angular isn't detecting the change in the parents data (fair enough because it hasn't technically changed) so it isn't sending the "latest" value back down to the child.
My question is, how can I get the child text input to always show the correct value from the parent, even if it technically hasn't "changed"?

better solution
#Jota.Toledo's good comment made me realise that my approach, although it did serve as a quick workaround for me at the time, it's not a good one, so I actually went and made some changes to my project that can work for you as well, also following his suggestion of
Delegating that "validation" logic into the child component
while keeping the validation definition in the parent as a function that's passed to the child as an #Input param.
This way I'd give the parent 2 public vars
a object (item)
a function (validation)
and change onValueChange function to only update the item.key1 as it will be already validated.
In the child add a new #Input param (validation) of type Function and use that function to validate the newValue inside the onBlur, before emiting the value to the parent.
I have the feeling that what I've written here might "sound" a bit confusing so I'm adding the code for what I'm trying to explain.
Parent
#Component({
selector: 'parent',
template: `
<div>
<p><b>This is the parent component</b></p>
<child [value]="item.key1"
[validation]="validation"
(valueChange)="onValueChange($event)">
</child>
<p><b>Variable Value:</b> {{item | json}} </p>
</div>
`
})
export class Parent {
item = {
key1: 'test value',
key2: 'another test value'
};
validation = (newValue) => {
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
return newValue;
}
onValueChange(newValue) {
this.item.key1 = newValue;
}
}
Child (leaving the template part out because it's unchanged)
export class Child {
#Input() value: string;
#Input() validation: Function;
#Output() valueChange = new EventEmitter();
onBlur() {
this.value = this.validation(this.value)
this.valueChange.emit(this.value);
}
}
previous answer (not a good approach)
I had a similar problem in the past and the way I solved was to clear my var and then giving it the final value inside a setTimeout without specifying the milliseconds.
in your parent component it would look something like this:
onValueChange(newValue) {
console.log('Initial: ' + newValue);
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
console.log('Final: ' + newValue);
this.item.key1 = '';
setTimeout(() => {
this.item.key1 = newValue;
});
}

However, if the first 4 characters stay the same, and you append
additional characters, upon blur the validation will still be applied
and the parent's value will get updated correctly, but the child will
preserve the "invalid" value - no more happy days.
So it looks to me like Angular isn't detecting the change in the
parents data (fair enough because it hasn't technically changed) so it
isn't sending the "latest" value back down to the child.
Correct. The fact is that angular is "caching" the latest value passed through the value input property, and as you recognize, you arent really pushing new values into it if you dont change the first 4 chars.
You can check this by adding ngOnChanges(changes) to your child and logging the changes value into console; in the above case nothing is logged, as no new value for value is pushed through.
You could overcome this by:
Enforcing that a new value is always pushed through the input property by wrapping the value in an object and unwrapping it in the child component. (Bad approach IMO)
Delegating that "validation" logic into the child component, so that only "validated" values are emitted through the output property.

You should create a Subject on the ParentComponent :
export class ParentComponent {
parentSubject:Subject<Item> = new Subject();
notifyChildren() {
this.parentSubject.next('new item value');
}
}
Pass it as Input() on the ChildComponent :
<child [parentSubject]="parentSubject"></child>
And finally subscribe to it on the ChildComponent :
export class ChildComponent {
#Input() parentSubject:Subject<Item>;
ngOnInit() {
this.parentSubject.subscribe(event => {
//do your stuff with the updated value
});
}

you can update your code for onValueChange method in parent.ts with following
onValueChange(newValue) {
this.item.key1 = null;
setTimeout(() => {
console.log('Initial: ' + newValue);
if (newValue.length > 4) {
newValue = newValue.substring(0, 4);
}
console.log('Final: ' + newValue);
this.item.key1 = newValue;
})
}

Related

Update child component from parent in Angular

I have struggling to make it work in Angular. I have a host component (parent) which is using a child one to render a dropdown list. The source of the list is been passed from the parent. So for example, if the parent pass 5 items on the source property, the child component will render 5 options for the dropdown list.
this is part the code where I call the child component:
parent.component.html
<ng-container>
<th mat-header-cell *matHeaderCellDef>
<app-column-header
[id]="column.id"
[name]="column.name"
[source]="myObject.options"
></app-column-header>
</th>
</ng-container>
parent.component.ts
export class ParentComponent {
#ViewChild(ChildComponent) ChildComponent;
// more code
private updateChildSource() {
this.child.updateDataSource(myObject.options);
}
}
This is working OK so far.
NOW, the challenges I am having is that the list of items to be passed needs to be dynamic (myObject.options). So, for example, the first time lets says I am passing 5 items. Angular takes those 5 items and render the child component properly. However, once the child component is already rendered and if I changes the source to be 2 items instead of 5 from the parent and pass the new source, the child component is not rendering the new items (2).
child.component.ts
export class ColumnHeaderComponent implements OnInit, OnChanges {
#Input() id: string;
#Input() name: string;
#Input() source: any[];
childField: any;
ngOnInit(): void {
const options = this.doStuffHere(this.source);
this.childField= {
id: this.id,
options,
};
}
updateDataSource(newSource: Option[]): void {
console.log(`print call from parent. old options:
${JSON.stringify(this.childField.options)} - new options: ${JSON.stringify(newSource)}`);
this.source= newSource;
const options = this.doStuffHere(this.source);
this.childField= {
id: id,
options,
};
}
ngOnChanges(changes: SimpleChanges) {
console.log('changed');
for (const propName in changes) {
const chng = changes[propName];
const cur = JSON.stringify(chng.currentValue);
const prev = JSON.stringify(chng.previousValue);
console.log(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
As mentioned before, the child component is receiving the original and new items, even the ngOnChanges method is capturing it and printing the values properly. But for some reason I don't know yet the child component is still rendering the old items (5) instead of the new ones (2).
Not sure, if I am missing something here? Or the question is clear enough to illustrated the problem I am facing.
Could you point me to the correct direction how to solve this? Thanks in advance.
As said Marek you can directly pass the list from your parent component as the input of your child component. The [list]="list" notation is already reactive.
Then you'll just have to use the list in the drop-down in your child component.
Note : Not useful here, but as #Input you can set a function instead of a variable. It will be triggered every time the input value change.

Angular Two-Way Data Binding and Watching for Changes in Parent Component

It seems there is no way to watch changes in the parent component when using two-way data binding.
I have a custom input component for collecting a tag list. Two-way data binding is setup and working between this component and its parent.
// the parent component is just a form
// here is how I'm adding the child component
<input-tags formControlName="skillField" [(tags)]='skillTags' (ngModelChange)="skillTagUpdate($event)">
</input-tags>
In the parent component how do you watch the bound variable for changes? While it's always up to date (I've confirmed this) I cannot find any guidance on reacting to changes.
I've tried:
ngOnChanges(changes: SimpleChanges) {
if (changes['skillTags']) {
console.log(this.skillTags); // nothing
}
}
And
skillTagUpdate(event){
console.log(event); // nothing
}
UPDATE:
TWDB IMHO is not what it is advertised to be. Whenever I arrive at this place where TWDB seems to be a solution I rearchitect for a service and or observable communication instead.
When you implement a two way binding of your own, you have to implement an event Emitter. The syntax for that is mandatory.
this means that you have a hook to listen to if the value changes.
Here is a demo :
<hello [(name)]="name" (nameChange)="doSomething()"></hello>
_name: string;
#Output() nameChange = new EventEmitter();
set name(val) {
this._name = val;
this.nameChange.emit(this._name);
}
#Input()
get name() {
return this._name;
}
counter = 0;
ngOnInit() {
setInterval(() => {
this.name = this.name + ', ' + this.counter++;
}, 1000);
}
Stackblitz
From what I know, this seems the less annoying way to use it, and any two way binding will follow the same rule no matter what, i.e. it ends with the Change word !
Your implementation is actually not two-way databinding, the parent and child component are just sharing a reference on the same skillTags variable.
The syntax [(tags)]='skillTags' is syntaxic sugar for [tags]='skillTags' (tagsChange)='skillTags = $event'
You need to implement tagsChange in the child component like this: #Output('tagsChange') tagsChange = new EventEmitter<any>();, then any time you want to modify tags into the children component, dont do it directly, but use this.tagsChange.emit(newValue) instead.
At this point, you'll have real two-way databinding and the parent component is the unique owner of the variable (responsible for applying changes on it and broadcasting changes to the children).
Now in your parent component, if you want to do more than skillTags = $event (implicitly done with [(tags)]='skillTags'), then just add another listener with (tagsChange)='someFunction($event)'.
StackBlitz Demo
Don't know if this is what you're looking for, but have you tried using #Input()?
In child component
#Input() set variableName(value: valueType) {
console.log(value);
}
In parent component
<input-tags formControlName="skillField" [(tags)]='skillTags'
[variableName]="skillTagUpdate($event)"></input-tags>
The input function is called every time the object binded to the function is changed.
you could listen to the change:
<input-tags formControlName="skillField" [tags]='skillTags' (tagsChange)='skillTags=$event; skillTagUpdate();'></input-tags>
or use getter and setter:
get skillTags(): string {
return ...
}
set skillTags(value) {
variable = value;
}
another approach:
export class Test implements DoCheck {
differ: KeyValueDiffer<string, any>;
public skillTags: string[] = [];
ngDoCheck() {
const change = this.differ.diff(this.skillTags);
if (change) {
change.forEachChangedItem(item => {
doSomething();
});
}
}
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
}}
1.you can use output(eventemitter)
2.easiest solution is rxjs/subject. it can be observer and observable in same time
Usage:
1.Create Subject Property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
2.When event happend in child page/component use :
logout(){
this.authService.loginAccures.next(false);
}
3.And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {this.isLoggedIn = isLoggedIn;})
}
Update
for two-way binding you can use viewchild to access to your child component items and properties
<input-tags #test></<input-tags>
and in ts file
#ViewChild('test') inputTagsComponent : InputTagsComponent;
save()
{
var childModel = this.inputTagsComponent.Model;
}

reactjs input element loses focus after keystroke

So I am using a hash to store the values of dynamically created rows of input values and I lose focus on the input I am modifying after entering only one character. I think the solution to this may be to use refs to refocus on only the last input changed, but I couldn't get it to work, as I wasn't able to figure out how to specify which element was last changed. Advice on how to solve this is appreciated.
The code below dynamically creates input boxes, and looks up their values based on the unitPriceValueHash. Each variant has an id, and id is used as the key to the hash.
I created a codepen to try and recreate the problem, but the issue im facing doesn't show up in code pen. In my actual app I press 1 for example in the input box, then the cursor is not on the input box anymore.
https://codepen.io/ByteSize/pen/oogLpE?editors=1011
The only difference between the codepen and my code appears to be the fact the the inputs are nested inside a table.
CreateItem(variant) {
const unitPriceValueHash = this.props.unitPriceValueHash
return {
variant_title: variant.variant_title,
variant_price: variant.variant_price,
unit_cost: <TextField
type="number"
onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
key={variant.id}
value={unitPriceValueHash[variant.id] || ''}
/>
};
}
Below is the change of state that modifies the hash
handleUnitPriceChange (id, event) {
const unitPriceValueHash = this.state.unitPriceValueHash
unitPriceValueHash[id] = event
console.log(unitPriceValueHash)
this.setState({unitPriceValueHash: unitPriceValueHash});
//this.updateVariantUnitCost(id, event);
}
There's a couple problems with the code you've shared.
Don't use inline functions. Each render, the function is created again which means that when react compares the props, it looks like the function is different (it is a new/different function each time!) and react will re-render.
Don't modify any objects which exist in the state, instead create a new object. If you modify an object that exists in the state, you're essentially saying you don't want renders to be consistent and reproducible.
I've re-posted your original code with the issues highlighted
CreateItem(variant) {
const unitPriceValueHash = this.props.unitPriceValueHash
return {
variant_title: variant.variant_title,
variant_price: variant.variant_price,
unit_cost: <TextField
type="number"
onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
// ^^^^ - inline functions cause react to re-render every time, instead - create a component
key={variant.id}
value={unitPriceValueHash[variant.id] || ''}
/>
};
}
handleUnitPriceChange(id, event) {
const unitPriceValueHash = this.state.unitPriceValueHash
unitPriceValueHash[id] = event
// ^^^^ - please, please - don't do this. You can't mutate the state like this.
// instead, do the following to create a new modified object without modifying the object in the state
const unitPriceValueHash = Object.assign({}, this.state.unitPriceValueHash, { id: event });
this.setState({ unitPriceValueHash: unitPriceValueHash });
}
In regards to the inline-function, generally the recommendation is to create a new component for this which takes the value as a prop. That might look like this:
class UnitCost extends PureComponent {
static propTypes = {
variantId: PropTypes.number,
variantValue: PropTypes.object,
onUnitPriceChange: PropTypes.func,
}
handleUnitPriceChange(e) {
this.props.onUnitPriceChange(this.props.variantId, e)
}
render() {
return (
<TextField
type="number"
onChange={this.handleUnitPriceChange}
value={this.props.variantValue || ''}
/>
);
}
}
CreateItem(variant) {
const unitPriceValueHash = this.props.unitPriceValueHash
return {
variant_title: variant.variant_title,
variant_price: variant.variant_price,
unit_cost: (
<UnitCost
key={variant.id}
variantId={variant.id}
variantValue={unitPriceValueHash[variant.id]}
onUnitPriceChange={this.handleUnitPriceChange}
/>
),
};
}
Regarding your concerns about focus, react generally won't lose your object focus when re-rendering, so don't ever, ever re-focus an object after an update for this reason.
The only time react will lose focus, is if it completely discards the current DOM tree and starts over from scratch. It will do this if it thinks a parent object has been replaced instead of modified. This can happen because of a missing key prop, or a key prop that has changed.
You have not posted enough code for us to investigate this further. If you want more help you should build a minimum reproducible example that we can run and test.
The solution to this problem had me use an intermediate state to store the value of the input field on change, and a submit AJAX request on an onBlur
class TextFieldWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.variantValue[this.props.variantId] || '',
}
this.handleUnitPriceChange = this.handleUnitPriceChange.bind(this)
this.updateValue = this.updateValue.bind(this)
}
updateValue(value){
this.setState({
value: value,
});
}
handleUnitPriceChange() {
this.props.onUnitPriceChange(this.props.variantId, this.state.value);
}
render(){
return (
<TextField
type="number"
id={this.props.variantId}
key={this.props.variantId}
onChange={this.updateValue}
onBlur={this.handleUnitPriceChange}
value={this.state.value}
/>
);
}
}

Set React Input Field Value from JavaScript or JQuery

How can you programmatically set the value of an input field generated by React, either with vanilla JS or JQuery?
I've tried the following and nothing seems to work.
$(obj).val('abc');
$(obj).attr('value', 'abc');
$(obj).keydown();
$(obj).keypress();
$(obj).keyup();
$(obj).blur();
$(obj).change();
$(obj).focus();
I've also tried to simulate keyPress (as suggested here) events but it doesn't seem to work either.
simulateKeyPresses (characters, ...args) {
for (let i = 0; i < characters.length; i++) {
this.simulate('keyPress', extend({
which: characters.charCodeAt(i),
key: characters[i],
keyCode: characters.charCodeAt(i)
}, args));
}
}
Out of all the answers and after a lot of googling, I found this to be working
function changeValue(input,value){
var nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
"value"
).set;
nativeInputValueSetter.call(input, value);
var inputEvent = new Event("input", { bubbles: true });
input.dispatchEvent(inputEvent);
}
We are using window.HTMLInputElement.prototype that is HTMLInputElement. An interface that provides special properties and methods for manipulating the options, layout, and presentation of input elements.
Then we will use Object.getOwnPropertyDescriptor() method to set input value. Last we will dispatch change event on the input to simulate with React onChange
Here is a detailed explanation of this answer: https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04
As showcased in the react test utils docs in the simulate section, you can see what they're basically doing is changing the DOM node value and then triggering an input event.
What you could do is something like the following, calling it with your input DOM element and new value.
const changeValue = (element, value) => {
const event = new Event('input', { bubbles: true })
element.value = value
element.dispatchEvent(event)
}
Depends on how you defined your components though, if for example you're expecting an enter keypress, you'll have to dispatch the matching event.
This is a well tested solution that works for IE11 as well as other browsers. It is the createNewEvent that differentiate this solution form the others in here I guess. The setReactValue method also returns the changed value.
function setReactValue(element, value) {
let lastValue = element.value;
element.value = value;
let event = createNewEvent("input", element);
event.simulated = true;
let tracker = element._valueTracker;
if (tracker) {
tracker.setValue(lastValue);
element.dispatchEvent(event);
}
return lastValue;
}
function createNewEvent(eventName, element) {
let event;
if (typeof(Event) === 'function') {
event = new Event(eventName, {target: element, bubbles:true});
} else {
event = document.createEvent('Event');
event.initEvent(eventName, true, true);
element.addEventListener(eventName, function(e) {
e.target = element;
});
}
return event;
}
This will depend on the browser, but for text inputs the onChange call is listening to input events
element.value = 'new value';
var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
According to this answer, you can get react instance from dom.
Assume the obj is a dom element.
function findReact(dom) {// from https://stackoverflow.com/a/39165137/4831179
for (var key in dom) {
if (key.startsWith("__reactInternalInstance$")) {
var compInternals = dom[key]._currentElement;
var compWrapper = compInternals._owner;
var comp = compWrapper._instance;
return comp;
}
}
return null;
};
var instance = findReact(obj);
console.log(instance.state);//try to modify the form and check what's here
instance.setState({
//the state name from the previous step
});
instance.submit();//something like this
You can achieve this by using ReactDOM(https://facebook.github.io/react/docs/react-dom.html) and Jquery, is not very common to manipulate like this but it works:
var ctx = this;
//Save the context of your class to the variable ctx, since inside $/Jquery the this is a reference to $/Jquery itself.
$(ReactDOM.findDOMNode(ctx.refs.myInput)).val('abc');
And your input must have a ref property to React find it:
<input type="text"
className="form-control"
ref="myInput"
placeholder="text"
/>
I had the same problem here using React inside another framework built with JQuery.
But in my case, I was needed to change only one field. Please, check if works for you:
import React, { useEffect, useRef } from 'react';
const Exemple = () => {
const [value, setValue] = useState();
const inputRef = useRef(null);
useEffect(() => {
const myInputRef = inputRef.current;
myInputRef.onchange = e => setValue(e.target.value)
}, [])
return (
<div>
<input ref={inputRef} id={my_id} />
</div>
);
}
export default Exemple;
You can use the state to directly update the value of your text field.
Let the value of text input in the state be:
state = {
textInputValue: ""
};
This is how you define your text input in React
<input type="text"
className="form-control"
name="my-text-input"
placeholder="text"
value={this.state.textInputValue}
onChange={this.onTextInputChange}
/>
Once you have defined your text input, you can update the value of your text input by just changing your state say this.setState({textInputValue: 'MyText'}) from within your react component. After that, you can normally update the value of the text field using
onTextInputChange(event) {
let newText = event.target.value;
return this.setState({textInputValue: newText});
}
I don't know what kind of scenario you are facing. Since React creates and maintains it's own virtual DOM, you can't manipulate the DOM elements with Jquery or Javascript from outside React. However if you need to get data from outside, use componentWillMount() in your React component to write code that gets data from your required data source and set it to the state of your TextInput
componentWillMount() {
// Code to get your data into variable 'defaultTextValue'
this.setState({textInputValue: defaultTextValue});
}
Try to reassign the entire html content like
$("html").on("DOMNodeInserted DOMNodeRemoved change", "body", function(){
$("body").html($("body").html());
});
// or call a simple function with $("body").html($("body").html());
I did that to reassign html and apply events again on svg tags in jquery after raw code injection ... maybe that ll work for this case too..
Try .on() method on the events either.
I've made a codepen with a working example of what I believe Dani Akash was trying to say. It is important to know that in React, setState() causes the component to rerender, hence in my example passing the new state as a prop to the child component.
https://codepen.io/tskjetne/pen/mmOvmb?editors=1010
First I render the Parent component I created.
The parent component contains a button and another React component I created InputWithButton
The Parent constructor gets called first, setting the Parent components state to the object {value: "initial value"}
The setValueInParent is a click handler I bind to the button in the Parent component. It sets the Parent components state which causes a rerender.
The Parent component passes its state.value as a prop to the InputWithButton component.
The InputWithButton component is very similar to the parent. Although, in the constructor it sets the state value to be the value prop passed in from the parent.
Other than that the InputWithButton component works more or less the same way as the Parent component.
This enables you to change the input value by typing in the input field, clicking a button in the same component as the input field, and passing in a new value as a prop from a parent.

Pass properties from parent component to all transcluded children component in Vue

I would like to pass some properties from a parent to all of his children when those are transcluded (content distribution syntax). In this case, the parent doesen't know (as far as I know) his children, so I don't know how to proceed.
More specificly, I want a way to write this :
<my-parent prop1="foo" prop2="bar">
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
</my-parent>
Instead of having to write this :
<my-parent prop1="foo" prop2="bar">
<my-children prop1="foo" prop2="bar"></my-children>
<my-children prop1="foo" prop2="bar"></my-children>
</my-parent>
Is it possible ? Thanks.
Props allow data flow only one level. If you want to perpetuate data, you can use an event bus instead.
Instantiate an event bus with an empty Vue instance in your main file.
var bus = new Vue();
Then in your parent, emit the event with data to be passed
bus.$emit('myEvent', dataToBePassed);
Listen for myEventanywhere you want to pick up the data. In your case, it is done in your child components
bus.$on('myEvent', function(data) {
.....
});
Here is my solution, that's probably not a great deal, but that's the cleanest solution for what I want to do right now. The principle is to create computed properties that will use own component prop if they exist, or get $parent values otherwise. The real prop would then be accessible in this._prop.
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
computed: {
_prop1: function() {
return this.prop1 || this.$parent.prop1;
},
_prop2: function() {
return this.prop2 || this.$parent.prop2;
}
}
});
Here is a mixin generator that does that in a more elegant way, and with, possibly, multiple levels :
function passDown(...passDownProperties) {
const computed = {};
passDownProperties.forEach((prop) => {
computed["_" + prop] = function() {
return this[prop] || this.$parent[prop] || this.$parent["_" + prop];
};
});
return { computed };
}
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
mixins: [passDown("prop1", "prop2")]
});
At this point (I'm not a vue expert) I just could think in this solution.
Assign every component's props is boring I agree, so why not doing it programmatically?
// Create a global mixin
Vue.mixin({
mounted() { // each component will execute this function after mounted
if (!this.$children) {
return;
}
for (const child of this.$children) { // iterate each child component
if (child.$options._propKeys) {
for (const propKey of child.$options._propKeys) { // iterate each child's props
// if current component has a property named equal to child prop key
if (Object.prototype.hasOwnProperty.call(this, propKey)) {
// update child prop value
this.$set(child, propKey, this[propKey]);
// create a watch to update value again every time that parent property changes
this.$watch(propKey, (newValue) => {
this.$set(child, propKey, newValue);
});
}
}
}
}
},
});
This works but you will get an ugly vue warn message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
I'm not sure if this is a good solution but it works, so if you decide to use just keep in mind Global-Mixin recomendations:
Use global mixins sparsely and carefully, because it affects every
single Vue instance created, including third party components.
Please see a full example at https://github.com/aldoromo88/PropsConvention
Hope it helps

Categories