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
Related
There are multiple tabs like this:
<Menu.Item className="tab-title tab-multiple" key="key">
<p className="tab-title-text">
Tab title
<span className="items-counter">{showText}</span>
</p>
</Menu.Item>
the one that is the active/selected one, beside of its original class (tab-title tab-multiple) it also has active and its class looks like this: active tab-title tab-multiple
I want to show that element only if the class contains "active".
Is there a way to do this in React? Without taking in account onClick.
Tried with a ternary but it seems it does not work:
{element.classList.contains('active') ? (
<span className="items-counter">{showText}</span>
) : (<></>)}
Normally, you don't have to do that in React because you drive the classes on the element from state information in your component, and so you just look at that state information rather than looking at the class list. Your best bet by far is to do that, rather than accessing the DOM class list later.
If the active class is being added by something outside the React realm that's operating directly on the DOM element, you'll have to use a ref so you can access the DOM element.
To create the ref:
const ref = React.createRef();
To connect it to your React element, you add the ref property;
<Menu.Item className="tab-title tab-multiple" key="key" ref={ref}>
Then when you need to know, you check the current property on the ref:
if (ref.current && ref.current.classList.contains("active")) {
// ...
}
Beware that if you do that during a call to render (on a class component) or to your functional component's function, on the first call the ref will be null and on subsequent calls it'll always refer to the element for the previous version of the component. That element will probably get reused, but not necessarily.
React is driven by the data model (props & state). Use whatever data property you use to assign the active class name, to also hide/show the contents.
Another option is to use CSS:
.items-counter {
color: red;
}
.tab-title:not(.active) .items-counter {
display: none;
}
<div class="tab-title tab-multiple" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Not Active</span>
</p>
</div>
<div class="tab-title tab-multiple active" key="key">
<p class="tab-title-text">
Tab title
<span class="items-counter">Active</span>
</p>
</div>
You need to have an indicator, that maintains the active class.
let className ="";
if(isActive)
{
className = className +" active"; // props.isActive in case of child component
}
Now that you have added the className based on the flag.
instead of checking for,
if(element.classList.contains('active'))
you can check for,
if(isActive)
This is applicable for subcomponents also, where you read the isActive flag through props.
Is it possible to pass a variable into a passed down action and do so entirely in-template?
eg.
Parent Component Template
{{child-component model=model itemClick=(action "doStuffWithItem")}}
Child Component Template
{{#each model.items as |item|}}
{{item-component click=(action itemClick item)}}
{{/each}}
..that doesn't work, but it shows an idea of the desired behaviour.
What I current have to do is make an action in item-component to trigger the passed down action with the variable.
eg.
Parent Component.js
actions: {
doStuffWithItem(item) {
...do some things
}
}
Parent Component Template
{{child-component model=model itemClick=(action "doStuffWithItem")}}
Child Component.js
actions: {
click(item) {
this.itemClick(item);
}
}
Child Component Template
{{#each model.items as |item|}}
{{item-component itemClick=itemClick}}
{{/each}}
What I'm trying to ask, is there a way to skip creating the wrapper action in the child component.js? Is there an Ember helper where this just works?
eg.
{{#each model.items as |item|}}
{{item-component click=(pass-var-to-action itemClick item}}
{{/each}}
Um, your first example works. So yes, its possible.
Your second example that you don't like however does not make sense. This code:
actions: {
click(item) {
this.itemClick(item);
}
}
is not doing anything, because you dont use the click action inside the child-component.
It's totally unclear what your item-component does. Once you pass click, next itemClick. Be careful: never call a property click. This is a name collision with the click event handler method. See here under * Event Handler Methods*.
This is a bit an strange concept, but could be your problem.
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.
I have an Angular 6 app with a <div> that gets populated via an [innerHTML] binding. How can I apply target='_blank' to all the links inside this div?
What I've tried:
So far, I've tried creating a directive that wraps the div and, after change detection gets run, pulls up a list of child <a> tags and applies a target='_blank' attribute. So far, I have not been able to get ContentChildren to access any of the links: It just pulls up an empty list.
Does anyone have experience doing this, or is there a more elegant solution?
#Directive({
selector: '[appExternalLink]'
})
export class ExternalLinkDirective implements AfterContentChecked, AfterViewChecked {
#ContentChildren('a', {descendants: true}) links: QueryList<any>;
#Input() appExternalLink: string;
constructor() {
console.log('HELLO FROM APPEXTERNALLINK');
}
ngAfterContentChecked() {
console.log(this.links);
}
}
Then, when binding the content:
<div appExternalLink>
<div [innerHTML]="content"></div>
</div>
It turns out I was using the wrong approach trying to use ContentChildren.
With the below code, I was able to target all <a> tags in a div with the appExternalLink directive. Posting my solution so other people don't have to figure this out themselves:
import { AfterViewChecked, Directive, ElementRef } from '#angular/core';
#Directive({
selector: '[appExternalLink]'
})
export class ExternalLinkDirective implements AfterViewChecked {
constructor(private el: ElementRef) { }
ngAfterViewChecked() {
Array.from(this.el.nativeElement.querySelectorAll('a'))
.forEach((el: any) => {
el.setAttribute('target', '_blank');
});
}
}
You can use the <base> tag to default the target attribute of each anchor.
<base target="_blank">
You can inject this tag dynamically in ngOnInit and remove it in ngOnDestroy, but it will change the behaviour of any link.
If you want to change the behaviour of just the anchors inside a div, you can use
Array.from(document.querySelectorAll('.your-div-class a'))
.forEach(el => el.setAttribute('target', '_blank'))
This is the selector you're using
#ContentChildren('a', {descendants: true}) links: QueryList<any>;
What this does is look for any direct children of the component which have an id of a.
If you change your html to look like this:
<div appExternalLink>
<a #a>Link</>
<a #a>Link</>
</div>
Then your selector will find the links.
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.