Inherit attributes in component definition - javascript

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.

Related

Styles are not applied to HTML element after dynamically inserted to DOM - Angular

I wanted to add an "i" element as a child element to HTMLDivElement
but styles are not applied to the newly inserted "i" element.
How can I make sure to apply the same styles to newly added "i" element?
I want to apply the the styles that I have set in the scss file for the new element.
(when I am inserting the new element in the .ts file, I can set the same styles using JS(TS), but I intend to use the scss/css styles, is there any way to use the scss/css styles for the newly added elements?)
accommodation-view-data.html
<div class="star-rating" #starRating>
<i class="bi bi-star-fill"></i> <!-- styles are applied to this element successfully -->
</div>
accommodation-view-data.component.scss
i{
font-size: 1.6rem;
padding: 0 0.25rem ;
color: #ffbe0f;
}
accommodation-view-data.component.ts
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '#angular/core';
#Component({
selector: 'app-accommodation-view-data',
templateUrl: './accommodation-view-data.component.html',
styleUrls: ['./accommodation-view-data.component.scss']
})
export class AccommodationViewDataComponent implements OnInit, AfterViewInit {
#ViewChild('starRating')
starRatingElem!: ElementRef;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
/* When I append the the new element to the DOM,
* the styles that I have set in the scss file, are not applied for the new element */
const starIconElement = document.createElement('i');
starIconElement.classList.add('bi');
starIconElement.classList.add('bi-star-fill');
(this.starRatingElem.nativeElement as HTMLDivElement).append(starIconElement);
}
}
This is a normal behavior, When you write style that is integrated in the styleUrls, every single class is reworked to be specific to the component, it's the style scope (note the i[ng-content-lep-c52]in the screenshot below).
So adding an element in the dom like you do is not recommended, instead prefer toggling it thanks to a ngIf or here for stars with a ngFor or else you'll have hard times trying to make it dynamic and bind values with the component model.
Like you can see in this screenshot, the first element is properly matched with the scoped style while the new element isn't.
NOT RECOMMENDED AND DEPRECATED: To bypass this you can use the ::ng-deep pseudo class to make a style global, and therefor getting overrided by scoped style as you can see in this screenshot comming from this stackblitz.
You can find more information about styles in angular here

Accessing shared component element in DOM in Angular

Shared component: pro-image
This component has image element with id proImg
<img id="proImg" src="{{imgPath}}">
imgPath is #input variable.
This component is used in multiple components and each parent passes image path and image dimension to above shared component.
In this shared component.ts file, I'm trying to access <img> element by id to add some attributes to the element. But while access element by Id only last element is getting accessed.
I want to uniquely this image element each time its been used in other component similar to host selector. Please suggest how can I achieve.
Your issue is that they all have the same id property. id should be unique, per Mozilla docs for getElementById https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
One option, instead of using the id, use a ViewChild to access the DOM Element.
Change your template to this
<img #proImg [src]="imgPath">
And in your component you can access it this way:
class MyCmponent {
#ViewChild('proImg', {static: true}) proImg: ElementRef;
ngAfterViewInit() {
// you can access the native dom element like this:
this.proImg.nativeElement.style.height = "100px";
this.proImg.nativeElement.style.width = "100px";
}
}
Another option, if you are just setting styles or other properties, you can bind them in the template
<img [src]="imgPath" [ngStyle]="styles" [ngClass]="classes">
Component
class MyComponent {
styles = {
width: "100px",
height: "200px
}
classes = ['class1', 'another-class']
More on ngStyle https://angular.io/api/common/NgStyle
More on ngClass https://angular.io/api/common/NgClass

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

How to add several directives to a single component

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

Categories

Resources