So i'm in need of creating list popout that can be attached to a trigger element showing the list below that element.
Consider the following design:
When the plus icon is clicked the list of links appears relative to it.
I figured the list could be easily acomplished with a simple component where you feed it an array of link descriptors and it renders a list, something like this:
export interface LinkDescriptor {
name: string,
destination?: string
}
#Component({
selector: 'app-navigation-popout',
template: `
<nav>
<ul>
<li *ngFor="let item of items">
<a [attr.href]="item.destination">{{item.name}}</a>
</li>
</ul>
</nav>`,
styleUrls: ['./navigation-popout.component.scss']
})
export class NavigationPopoutComponent {
#Input()
items: LinkDescriptor[];
}
Now that gets me the list of links, i could style them to look as the design.
The bit im struggling with is how to link the trigger, in this case a button elsewhere in the document, and the popout component. The other issue is how to manage the styling.
I know in order to absolutally position the popout relative to the trigger they both need to be within a relative or absolutally positions parent. However having to remember to do that every time this is used adds maintanence overhead.
I have considered some form of directive that i could decorate the trigger button with and it will dynamically insert the popup below the trigger item, however im not too keen on that idea as it means directly manipulating the DOM outside the scope of the trigger element, IE wrapping it.
The other option i considered was using ng-content and providing the trigger element within the declaration of the nav popout.
So something like this:
<app-navigation-popout>
<button>+</button>
</app-navigation-popout>
However i'm not too keen on that idea either.
Just wondering how others would tackle this problem of relating the two elements together and position one component relative to the other element it is linked to.
Thanks
So i decided on a solution and thought i would share in case others found it usefull.
So i split it down into two compoenents, one that displays a list of links:
export interface LinkDescriptor {
name: string,
destination?: string
}
#Component({
selector: 'app-links-list',
template: `
<nav>
<ul>
<li *ngFor="let item of items">
<a [attr.href]="item.destination">{{item.name}}</a>
</li>
</ul>
</nav>`,
styleUrls: ['./links-list.component.scss']
})
export class LinksList{
#Input()
items: LinkDescriptor[];
}
I then created a second component whose job it is to wrap the trigger and the links list. This second component deals with displaying and hiding the links and positioning them.
#Component({
selector: 'app-links-list-popout',
template: `
<span class="container">
<ng-content></ng-content>
<app-links-list [items]="items" *ngIf="showList"></app-links-list>
</span>`,
styleUrls: ['./links-list-popout.component.scss']
})
export class LinksListPopout {
#Input()
items: LinkDescriptor[];
showList: boolean;
toggle() {
this.showList = !this.showList;
}
}
Now when i want to implement a list of links i can do so like this:
<div>
<app-naivgation-list-popout [items]="[{name: 'link', destination: '/place'}]" #linksPopout>
<button (click)="linksPopout.toggle()">+</button>
</app-naivgation-list-popout>
</div>
Related
I need to add class to an element on an individual element scroll. I created a slackblitz example. I know how to add a class on whole body scroll. But, I need to add on particular element scroll.
In this example I need to add class on scrolling the div#paragraph.
Thanks in advance.
https://stackblitz.com/edit/angular-changeclassonelementscroll
You can create a directive that listens to its host scroll event. Something like would work:
#Directive({
selector: '[appScroll]'
})
export class ScrollDirective {
#Input() scrollClass: string;
constructor(private el: ElementRef, private renderer: Renderer2) { }
#HostListener("scroll", [])
onScroll() {
if (this.el.nativeElement.scrollTop > 20) {
this.renderer.addClass(this.el.nativeElement, this.scrollClass)
}
}
}
I forked your code example here
PS: try to avoid accessing the DOM directly using document.getElementById. Always use Angular utilities for that.
You could try adding an event to the paragraph element in your template:
<div id="paragraph" (scroll)="onDivScroll()">
and then add a function in your component that gets called
onDivScroll(){
this.document.getElementById('paragraph').classList.add('green');
}
you'll have to add your additional logic as needed, but you should be able to turn the individual element text green this way.
I would like to create a parent directive which shows or hides children based on the values of the children. To do this, i've taken the approach of a parent structural directive, and a child directive with values. For simplicity without the values:
<div *appParent>
<div appChild>1</div>
<div appChild>2</div>
<div appChild>3</div>
<div appChild>4</div>
</div>
To access the children, I use the following line in the parent directive:
#ContentChildren(AppChildDirective, { read: AppChildDirective, descendents: true }) children: QueryList<AppChildDirective>;
This query list is always empty. However, when I change it to a non-structural, it works fine. Stackblitz demo here
I assume this is due to the fact the structural directive creates a parent ng-template, which #ContentChildren then looks inside to find the original component, meaning that the query actually goes nowhere.
What approach can I take to access the children of the original component and not the template? Or do I need to take another approach to handle my requirements?
ContentChildren seem to not work on structural directives. However, this can be achived by injecting the parent directive in the child and then registering the child in the parent by calling a function.
#Directive({
selector: '[appChild]'
})
export class ChildDirective {
constructor(parent: ParentDirective) {
parent.registerChild(this);
}
}
#Directive({
selector: '[appParent]'
})
export class ParentDirective {
registerChild(child: ChildDirective) { /*...*/ }
}
Side notes
If you also want to be able to use the child directive without the parent directive, change the child's constructor like this to make the parent optional:
constructor(#Optional() parent: ParentDirective) {
parent?.registerChild(this);
}
You can also use this approach recursively by injecting a directive in its own constructor. If you do so, also add #SkipSelf() in the constructor to really get the parent:
#Directive({
selector: '[appRecursive]'
})
export class RecursiveDirective {
constructor(#Optional() #SkipSelf() parent: RecursiveDirective) {
parent?.registerChild(this);
}
registerChild(child: RecursiveDirective) { /*...*/ }
}
I am trying to apply styling to a child of a custom component.
Selector:
<custom-component-example></custom-component-example>
Inside custom-component-example.html:
<button>
<ng-content></ng-content>
</button>
If I were to use style like this:
<custom-component-example style="color: green">some text</custom-component-example>
Or like this:
<custom-component-example [ngStyle]="{'color': green}">some text</custom-component-example>
The button text will not get green. The styling could be anything (for example font-weight or size or anything really).
I have also tried the solution to this topic:
Best way to pass styling to a component
But that also does not apply to the child element (button in the example above).
How do I pass any given styling and apply it to the child element, in the case of the example, how would I pass styling (through the custom-component-example selector) and apply it to the button and the button's text?
Thanks in advance.
You should never alter the child style from the parent, instead here is what you should do :
Apply a class to the parent (let's say green-button).
In the child's css you need to check that does my parent has a class green-button, if yes then it should change it's color.
In the child's css file ->
:host-context(.green-button) button{
color : green
}
You should not transfer styles from parent to child essentialy because it spoils the ViewEncapsulation goodness that Angular is proud of.
Here is some refrence . : Link
Also, the child component should be responsible for how does it button looks. The parent should be concerned about itself. In the future, if you will have two children to your parent, it will be difficult to manage what style to pass to what child.
Using this method, altering style is not only easy but also manageable.
Upvote and mark as solved if I was able to help.Cheers.
You need to pass the style property to the child component using the #Input() like
Your child component HTML code should look like
<div [className]="tableCss">
</div>
Your child component ts file code shoule look like
#Input() tableCss: string;
Your Parent component should look like
<app-list [tableCss]="'table-responsive table-borderless table-grid mt-4 border-top border-primary'"></app-list>
Try to change styles into [styles]
custom-component-example.html
<button [ngStyle]="styles">
<ng-content></ng-content>
</button>
custom-component-example.ts
#Input() styles: any = {};
Use,
<custom-component-example [styles]="{color: green}">some text</custom-component-example>
If you would like to use input and styles without deep selectecting of css like that:
a > b > c {
color: green;
}
Change this class to this:
class CustomComponentExample {
#Input() styles;
}
Set styles for this input:
<custom-component-example [styles]="{'color': green}">some text</custom-component-example>
Use this property in your component:
<button [ngStyle]="styles">
<ng-content></ng-content>
</button>
Try this:
Add a class on that component into your template file like this example bellow. (class='toggle-button').
<custom-component-example class="toggle-button"> Button name </custom-component-example>
Use ::ng-deep to your scss file for styling this class and add !important to that parameter.
::ng-deep .toggle-button { .switch-btn {
width: 80px !important;
height: 40px !important;}}
*"switch-btn" class is a class into the parent component.
I currently need to be more specific in my functionality for a class toggle that needs to only occur in the children within the element being clicked. I have a class of ".node" and when this is clicked it should toggle a class on it's child ".node-dropdown". I have multiple of these nodes with (click)="showNodeDropdown" and currently all the node dropdowns are being hidden or shown at the same time.
HTML
<div class="node" *ngIf="authService.isInternalUser()" (click)="showNodeDropdown()">
<span class=" fa fa-clock-o node-icon"></span>
<p>Menu Title</p>
Keep in mind there are multiple of these elements using (click)="showNodeDropdown()" throughout my page and this is just one example.
JS
showNodeDropdown() {
$('.node-dropdown').toggleClass('hidden');
}
I tried changing my JS to be something like $(this).children('.node-dropdown).toggleClass('hidden');
however (this) is currently defined only as "object Object"
Is there a way I can get the element that was clicked and set it to (this) so that I can toggle only on it's children? Thanks in advance for any help.
Here are two Angular ways to get the clicked element:
Pass the event target to the handler:
<div (click)="showNodeDropdown($event.target)" ... >
Define a template reference variable, and pass it to the handler:
<div #myDiv (click)="showNodeDropdown(myDiv)" ... >
In both cases, the event handler can be written as:
showNodeDropdown(element: HTMLElement) {
let nodes = element.querySelectorAll(".node-dropdown");
for (let i = 0; i < nodes.length; i++) {
nodes[i].classList.toggle("hidden");
}
}
or, using jQuery:
showNodeDropdown(element: HTMLElement) {
$(element).children(".node-dropdown").toggleClass("hidden");
}
It is recommended not to use jQuery inside of Angular Applications. If you need to select DOM Elements use #ViewChild instead:
https://angular.io/api/core/ViewChild
Here an example:
Controller:
#ViewChildren(TestComponent) test: QueryList<TestComponent>
Template:
<ng-container *ngFor="let player_ of players; let i = index">
<test></test>
</ng-container>
to print the <test> Elements just do the following inside the TypeScript Controller:
console.log(this.test)
So, you can access them like any variabled declared in the Controller.
You can pass $event object here:
(click)="showNodeDropdown($event)"
and then use
showNodeDropdown(event) {
$(event.target).toggleClass('hidden');
}
My recomendation is not use jquery, instead create a Directive
import { Directive, HostListener, HostBinding } from '#angular/core';
#Directive({
selector: '[appNodeDropdown]'
})
export class NodeDropdownDirective {
#HostBinding('class.hidden') isOpen = false;
#HostListener('click') toggleOpen(evenData: Event){
this.isOpen = !this.isOpen;
}
}
and use it like this
<div appNodeDropdown *ngIf="authService.isInternalUser()">
of course remember to register the directive in the "Declarations" array of module of the component.
Although I know it is best not to use JQuery in my angular application thanks to #messerbill , I still needed to come up with an answer to handle this issue using JQuery. I was able to set an ID to each div that utilized this (click) and pass the ID through the click like so:
<div class="node" id="booking-node" (click)="showNodeDropdown('#booking-node')">
showNodeDropdown (id: string) {
$('.node-dropdown').addClass('hidden');
$(id).children('.node-dropdown').toggleClass('hidden');
}
This allowed me to gain the results that I was looking for. Keep in mind this is not the best answer, as not using JQuery at all would be the ideal solution, however I did not have this option so this is the JQuery work around.
Hello first of all I must say I am sorry but I don't know how to express the question better, is the reason I am not being able to find an answer by myself.
What I'm talking about is how to load a component inside another one, I need to indicate it in the directive. Here is a very small example that I did from scratch because I am not able to find the right syntax:
http://plnkr.co/edit/gFsqGJmmayOsewL3EfLf
import {Component} from 'angular2/core'
import {Prueba} from './prueba'
#Component({
selector: 'my-app',
providers: [],
template: `
<div>
<h2>Hello {{name}}</h2>
<prueba></prueba>
</div>
`,
directives: [Prueba]
})
export class App {
constructor() {
this.name = 'Angular2'
}
}
So as you can see in app.ts there is a directives inside component, if I remove that it does not work. I am not 100% sure why, but it's how I learned.
So next step, I wanted to have several components, so I can have Prueba and another that adds something extra (for starters, another "phrase", but the idea is to add something similar to THIS: http://plnkr.co/edit/SVPNwk?p=preview ). However I find myself unable to find the right syntax, anything I try makes even this simple example to fail.
As I said, I do not understand what am I missing, I have a new component, I import it, I use the selector, and so on, but it simply explodes. What concepts am I missing?
If I am still not explaining myself properly enough, this is the theoric concept I am talking about:
angular.io/docs/ts/latest/cheatsheet.html (I cannot post more than two links... anyway its the #Component part, that's the documentation I'm checking out).
In Angular2 there is a difference between a component and a directive:
A component gathers a view (template) with some properties and processing (the component class)
There are two kinds of directives:
Attribute directive. It changes the appearance or behavior of a DOM element
Structural directive. It changes the DOM layout by adding and removing DOM elements.
A component can be used in another component using its selector. You need to explicitly define it in the directives attribute of the container component. Whereas the attribute is called directives, you can put in it both components and directives. You can also provide parameters to a component and react on its events.
Here is a sample:
Sub component
#Component({
selector: 'sub',
template: `
<div>Sub</div>
`
})
export class SubComponent {
}
Container component:
#Component({
selector: 'comp',
template: `
<div>
<sub></sub>
</div>
`,
directives: [ SubComponent, AnotherComponent ]
})
export class ContainerComponent {
}
A directive will apply on an existing element also based on it selector.
Here is a sample:
Sub component
#Directive({
selector: '[dir]'
})
export class DirDirective {
constructor(el: ElementRef) {
// el.nativeElement corresponds to the DOM element
// the directive applies on
el.nativeElement.style.backgroundColor = 'yellow';
}
}
Container component:
#Component({
selector: 'comp',
template: `
<div dir>Some text</div>
`,
directives: [ DirDirective ]
})
export class ContainerComponent {
}
The directives attribute
To tell a bit more about the directives attribute. If the component / directive isn't a platform one, you need to explicitly define into this directive. If not, the component / directive won't apply.
This attribute can accept several values since it's an array:
#Component({
selector: 'comp',
template: `
<div>
<sub></sub>
<another></another>
</div>
`,
directives: [ SubComponent, AnotherComponent ]
})
export class ContainerComponent {
}