How to add several directives to a single component - javascript

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 {
}

Related

Inherit attributes in component definition

I have a basic image component and within it there is an img element. Is there a way to pass the native img attributes to image without having to define them all, such as alt and have them all applied to the img tag?
Here is some pseudo code of what I mean:
#Component({
selector: 'image',
template: '<img [src]="src" [...attrs]>'
})
export class ImageComponent {
#Input() attrs;
}
<image img.alt="Some Alternative Value">
<another-component></another-component>
</image>
Yeah, this can be done (if it's what you're after ..)
#Component({
selector: 'image',
template: '<img #imgRef [src]="src">'
})
export class ImageComponent {
#ViewChild('imgRef') imgRef: ElementRef<HtmlElement>;
#Input() attrs;
ngAfterViewInit() {
// assuming attrs is a key/value object
Object.entries(this.attrs).forEach(([key, value]) => {
this.imgRef.nativeElement.setAttribute(....)
})
}
}
As far as I'm aware of, it is not possible to reflect attributes from the component host into the component view, without having to manually define #Input()s for every possible attributes.
An possible alternative, if it applies to you, is to project an img element from outside your image component, inside the component's view.
<image>
<img alt="Some Alternative Value">
<another-component></another-component>
</image>
That way, you basically have the img element exposed, where you can freely add attributes, among others.

angular 9 add class on individual element scroll

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.

Accessing ViewChildren/ContentChildren in a structural Directive

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) { /*...*/ }
}

Linking component to separate element, and displaying it relative to that element

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>

Customize component in Angular2/4 depending on where it is used

I have the following components in my application:
NavbarComponent
HomeComponent
ClassroomComponent
I use NavbarComponent in both HomeComponent and ClassroomComponent like this:
// home.component.html
<app-navbar></app-navbar>
..something related to HomeComponent
// classroom.component.html
<app-navbar></app-navbar>
..something related to ClassroomComponent
I want to customize navbar depending on where it is used: for example, if it's used in HomeComponent, I want it to be red, and if otherwise, then set its color to green.
Is there any way I can do this?
You can try to use the :host-context selector to define the component styles based on it's ancestor.
For your example it should be working like this:
// inside app-navbar css file
// where 'home-component' is the selector of your HomeComponent
:host-context(home-component) {
background: red; // NavbarComponent is red when inside HomeComponent;
}
:host-context(classroom-component) {
background: blue; // NavbarComponent is blue when inside ClassroomComponent;
}
More about special CSS selectors can be found here.
You can change your component in different ways
By using Provider(injection) to access global values
By using Event (publisher/subscriber methodology) procedure http://learnangular2.com/events/
You can define a #Input decorator in the NavbarComponent https://github.com/ionic-team/ionic/issues/5741 or http://www.concretepage.com/angular-2/angular-2-custom-event-binding-eventemitter-example

Categories

Resources