I have components 'Parent' and 'Child'.From Parent we can add or remove child. so children are dynamic. in Parent I have rendered Child component in loop like below
Parent.component.html
<child *ngFor="let child of children" [data]="child"></child>
Now in child component I have added a function called IsValid() to check child is valid or not
Child.component.ts
IsValid()
{
//check validity of component and return true if valid else false
}
in parent component I have a button called 'Save' I have to enable that button if all child's are valid else need to disable that button.
So I need a way to call Child components IsValid function for each child component from Parent and then determine the validity result and apply it to Save button to enable or disable
What I have tried
1.
I have emited valid or invalid result from child to parent and if any childs result is invalid i have disabled save button.
but problem here is : if I have added one child, make it valid, save button will be enabled. now I have added another child which is invalid so save button will be disabled but if I remove invalid child save button will be disabled though we have only one child which is valid.. since IsValid event get emmited only if current child get change.
2.
I can use something like this
<child #varName></child>
#ViewChild('varName') childElement;
and then from parent I can call
childElement.IsValid()
but since I have rendered childrens in loop how to give unique name in loop and how to add reference to that unique HTML tag in ts file.
I have created case here SlackBlitz
Any help will be appreciated. Thanks
You might want to use #ViewChildren
In your parent component:
#ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
areChildrenValid(): boolean {
const invalid = this.children.some(c => !c.IsValid());
return !invalid;
}
Note that children will be defined after AfterViewInit hook.
#angular/core provides ViewChildren and QueryList, probably that should help you.
<child #varName></child>
import { ViewChildren, QueryList } from '#angular/core';
#ViewChildren("varName") customComponentChildren: QueryList<YourComponent>;
this.customComponentChildren.forEach((child) => { let retult = child.IsValid(); })
You could use Component selector:
#ViewChildren(ChildComponent) childrenList: QueryList<ChildComponent>;
and run a loop through it and determine the validity.
Related
Suppose there's a table that needs to keep some state, for example, knowing the last element that was hovered over. To do so, we'd need a onMouseOver event on a child element like this:
<tr onMouseOver={handleMouseOver}></tr>
and then the component containing the table would re-render all the children after setState({lastHovered: e.target}) (something along those lines)
If the goal of hover is to make changes to just one row (let's say show/hide something without using CSS) is there a way to keep this state at a higher level than the row and only render changes in the <tr>s that are affected by the hover?
As an example, let's say there's a delay on hovering in or out of the table. When hovering in, children should show a tooltip, but only if hovered directly over. The table/parent need to maintain some state to keep track of this but is there a way to not have to re-render the entire list of children?
For Functional Components
Wrap all the tr elements inside a React.memo (read) to only listen for changes to its internal props. Also, place the onMouseOver event on the table itself and not every child immediate element, then check which child element is in "focus" (i.e., event.target).
Inside table component:
const [lastHovered, setLastHovered] = useState(null);
const [trProps, setTrProps] = useState(null);
return (
<table onMouseOver={event => setLastHovered(event.target) /* Also check if 'TR' type */ }>
<TableRows someData={trProps} />
</table>
)
Table rows component:
const TableRows = React.memo( ({someData}) => /* Triggers update only if 'someData' changes */
<> { someData.map( data => <tr>{data}</tr> ) } </>
)
Note: The above code isn't tested; written to demonstrate a possible solution.
For Class Components
If you're using class components, then create a custom TableRow element and add shouldComponentUpdate() (read) to control rerenders, or use PureComponents (read) instead.
What is the easiest way to retrieve the (id of the) node that was just clicked on in a <v-treeview>? There is the update:open event that emits an array of all open nodes. I could temporarily store the whole array and compare a new version with the old one to see which element was added or removed. But this seems to be a bit cumbersome, right? I want to use the id of the node to dynamically reload the children of the children of the node from the backend, so that the user has the feeling that the data is already in the frontend. Maybe it is possible to emit in the update:open event not the whole array, but only the current node, somehow like this: #update:open="onExpand(item)"? (This throws the error Property or method "item" is not defined.)
You could use VTreeView's label slot, which passes the item itself in its slot props. In that slot, you could render a span with a click handler that also includes the item:
<template>
<v-treeview :items="items">
<template #label="{ item }">
<span #click="onItemClick(item)">{{item.name}}</span>
</template>
</v-treeview>
</template>
<script>
export default {
methods: {
onItemClick(item) {
console.log(item.id)
}
}
}
</script>
You can use update:active with the return-object prop. It will return the full object instead of the node id.
https://vuetifyjs.com/en/api/v-treeview/#props
I have a React component I built for a popup. PopupContent will receive a DOM element or another React component as a child.
class PopupContent extends React.Component {
render() {
return(
<div>
{React.cloneElement(this.props.children, {closePopup:this.props.closePopup})}
</div>
);
}
}
The closePopup prop sets a flag to show/hide the popup
closePopup(event){
event.preventDefault();
this.setState({
popupInView: false
})
}
The reason to pass closePopup to child is to close the popup from the child component.
This setup works well if the child is a custom React component:
<PopupContent>
<ContentOfThePopup />
</PopupContent>
But I get the Unknown Prop Warning if the child is a DOM element.
Warning: React does not recognize the closePopup prop on a DOM
element.
<PopupContent>
<div>Content Of The Popup </div>
</PopupContent>
I could use techniques explained here to distinguish between a DOM element and a React component. But I wanted to check with the community if there is a better way
what does this.props.children contain?
Shouldn't you be iterating over it?
render() {
return React
.Children
// this is the jsx version of cloneElenemnt,
// better to use in a render function
.map(Child => <Child.type ...Child.props ...this.props>)
}
Clone Element
React.Children.map
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
You should ensure that you are not accidentally forwarding props that were intended to be interpreted by the parent component.
Also you can try {...this.props} format to pass your data instead of using cloneElement(element, this.props)
I've ended up checking the type of the child and conditionally removing the prop closePopup
Following condition will be true if the child is a HTML DOM element.
typeof this.props.children.type === 'string
I have implemented a 'tree' list of categories for an Angular2 (Typescript) app I am developing. This component is supposed to allow you to be able to click on a category name (no matter whether it's a category or sub-category) and this will show products of the category.
My 'category-tree' component is a separate component and it is used recursively so I can traverse the category hierarchy correctly. For each category a span is generated with a 'click' event binded to it. When clicked I use the emit function to broadcast this information back to the parent component in order to update some variables there.
This functionality is working for top-level categories but the click is not working correctly when it is on a child category. The
function which watches for the change does not receive any information.
Here is my code:
The function which logs out the information into my console. This is on the parent component:
changeCategory(event) {
console.log(event);
}
The html for the parent which holds the directive tag and the emit event name (categoryChange):
<div id='left-menu-wrapper'>
<div id='left-menu'>
<h1>{{title}}</h1>
<h2>Categories</h2>
<ul class="categories">
<category-tree [categories]="categories" (categoryChange)="changeCategory($event)"></category-tree>
</ul>
<div *ngIf="selectedCategory">
{{selectedCategory.name}}
</div>
</div>
<div *ngIf="!contentLoaded" class='spinner'></div>
</div>
<product-view [product]="selectedProduct"></product-view>
The child component:
import { Component, Input, Output, EventEmitter, forwardRef } from 'angular2/core';
#Component({
selector: 'category-tree',
templateUrl: './app/views/category-tree.html',
directives: [forwardRef(() => CategoryTree)],
outputs: ['categoryChange']
})
export class CategoryTree {
#Input() categories;
public categoryChange:EventEmitter;
constructor() {
this.categoryChange =new EventEmitter();
}
categoryClick(category) {
this.categoryChange.emit({
value: category
});
}
}
And the recursive component html:
<li *ngFor="#category of categories">
<span (click)="categoryClick(category)" [class.selected]="category === selectedCategory">{{category.name}}</span>
<ul *ngIf="category.sub_categories" class='sub-category'>
<category-tree [categories]="category.sub_categories"></category-tree>
</ul>
</li>
As you can see, I bind a click event to each category which is that current category iteration. This calls an emit function in the category-tree class with that information and broadcasts it back. Again this works with a parent category but not a child.
My thinking is that as a child's direct parent component isn't the app.component.ts this may be causing an issue? I'm not sure.
Any ideas?
Thanks
The problem here is that the emit can only talk directly to it's parent component.
Because of this, I found a very useful question and answer here which explained Service events and how to communicate with deep-level components using a service like this:
Global Events in Angular 2
I have the following Vue.js components, which basically are supposed to have a radiobutton-like behaviour:
// Parent Component
<template>
<child-component
v-for="element in elements"
</child-component>
</template>
<script>
import ChildComponent from './Child.vue'
export default {
components: {
ChildComponent
},
props: {
elements: Array
},
methods: {
activate(e) {
for (let i of this.$children) {
i.active = false;
}
if (e < this.$children.length) {
this.$children[e].active = true;
}
}
}
}
</script>
and
// Child Component
<template>
{{active}}
</template>
<script>
export default {
props: {
active: Boolean
}
}
</script>
This works fine but only the parent can decide to activate one of the children (and thus deactivate all others).
I want however also be able to allow each child to activate itself (and by a magic property of its parent, deactivate all other siblings).
Obviously I do not want each child to know about its siblings and mess with their .active prop (super bad design).
I would rather not have a children communicate back up to the parent and call some method (still bad design as I could only reuse the child components in parents that have activate() method).
Instead I would like the parent to listen to changes to all children active props and take action when one of them changes. That way the parent entirely encapsulates the radio button behavior.
How can this interaction be implemented in Vue.js?
Take a look at two-way binding: http://vuejs.org/guide/components.html#Prop_Binding_Types
This allows you to sync a property's value in both directions, meaning the parent or the child has access to change the variable. Then you can watch for changes on the parent and update accordingly.
I think a better option would be to create a RadioSet component, which then would house a number of radio buttons. This would eliminate your concern about a parent having to have the activate() method. You could simply pass in an object with a series of id and values that could be used to generate the buttons.