How to access the ::after pseudo selector using ElementRef in Angular - css

How can e.g. access and set the background-color of the ::after pseudo selector of the element below?
#ViewChild('infobubble', { read: ElementRef }) infoBubbleElement: ElementRef;
ngAfterViewInit(): void {
const elem = this.infoBubbleElement.nativeElement;
elem.style.backgroundColor = 'green';
}

I was presented with the same situation, it's not possible to access to ::before or ::after HTML element from elementRef; my solution was to create another DIV within the HTML element that I need to access, to modify it with a directive and change the background of the div that would be the new :: before
Example my directive:
#Directive({selector: '[appPrimaryColor]'})
export class PrimaryColorDirective implements OnInit {
#Input() backgroundColor = false;
constructor(
private elementRef: ElementRef,
private _renderer: Renderer2,
) { }
ngOnInit() {
// Set BackgroundColor
if (this.backgroundColor) {
this._renderer.setStyle(this.elementRef.nativeElement, 'background-color', COLOR);
}
}
}
And in HTML:
<div class="icon">
<!-- step-ok-effect is the new div-->
<div
appPrimaryColor
[backgroundColor]="true"
class="step-ok-effect"></div>
<span class="icon-{{ step.state ? 'check' : step.icon }}"></span>
</div>
</div>

Related

Is there a way to keep linked stylesheets localized to single components?

I have a component that relies on external stylesheets. I'm bringing the stylesheet into the component like this:
Child component
export default class Child extends Component {
render() {
return (
<div>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" />
...my code here...
</div>
);
}
}
But what's happening is this is forcing those styles onto the parent component as well.
Parent Component
export default class Parent extends Component {
render() {
return (
<div>
...code here...
<Child />
... more code here...
</div>
);
}
}
Is anyone aware of a way that I can keep that stylesheet link localized to just that child component so the styles aren't applied to the parent component as well?
Edit 2
Currently trying the shadow dom route, trying to pass down some children. Getting an error after the initial render saying Cannot read properties of undefined (reading 'children'). It does render the this.props.children initially...
import React, { Component } from 'react';
class MyComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
${this.props.children}
`;
}
};
export default class Child extends Component {
render() {
return (
<div>
<script>
{!customElements.get("my-component") && customElements.define('my-component', MyComponent)}
</script>
<my-component>
<h1>Hello from shadow</h1>
</my-component>
<h1>Hello</h1>
</div>
);
}
}
You can try CSS Modules. Add :local(.className) to the class you want to use in your code which is in the font-awesome-min.css file. Then import the styles to your component. For example import styles from './font-awesome-min.css' then use the module in your code. The styles will only apply to specific element and won't affect other elements in the document. So let's say you have a class called .usericon in your css you do this in the css file.
CSS
:local(.usericon){
fill: red;
}
React Code
import styles from './font-awesome-min.css'
export default function Profile(){
return (
<i className={styles.usericon}>User Icon</i>
)
}
One way to truly isolate your CSS is with Web Components. Web Components are a browser API that allows defining custom elements with their own "shadow DOM". If a style is defined inside the shadow DOM, it is truly sandboxed with no styles going in or out. You can use whatever selectors you like:
class FancyBox extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
.fancy-box {
border: solid 3px darkblue;
background: dodgerblue;
padding: 10px;
color: white;
font: 16px sans-serif;
}
</style>
<div class="fancy-box">
<slot></slot>
</div>
`;
}
}
customElements.define('fancy-box', FancyBox);
.fancy-box {
border: dashed 3px darkred !important;
background: crimson !important;
padding: 10px !important;
color: white !important;
font: 16px sans-serif;
}
<fancy-box>Safe in my shadow DOM</fancy-box>
<div class="fancy-box">I am affected by outside stylesheets</div>
Note the use of <slot></slot>. This is a placeholder for child elements of the component.
If I wanted to use this custom element from React, it needs to be defined separately so it only runs once.
class FancyBox extends HTMLElement { /*...*/ };
customElements.define('fancy-box', FancyBox);
class ReactFancyBox extends React.Component {
constructor() {
super();
this.state = { value: 'hello world!' }
}
handleChange(e) {
this.setState({ value: e.currentTarget.value });
}
render() {
return (
<div>
<fancy-box>
<strong>{this.state.value}</strong>
</fancy-box>
<input value={this.state.value} onChange={e => this.handleChange(e)} />
</div>
);
}
};

Why does my Web Component CSS not show? I am not using shadowDOM

I have a Native V1 component that is not using shadowDOM so I place my CSS in the <head>. But when someone else uses my component my CSS no longer works.
This only happens if their component does use shadowDOM.
Example Code for my component:
class MyEl extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `<div class="spaced"><button class="happy-btn">I'm Happy</button></div>
<div class="spaced"><button class="sad-btn">I'm Sad</button></div>`;
}
}
// Define our web component
customElements.define('my-el', MyEl);
button {
padding: 8px 20px;
}
.happy-btn {
background-color: pink;
}
.sad-btn {
background-color: #007;
color: white;
}
<my-el></my-el>
My CSS is loaded into the <head> tag since I am not using shadowDOM. But once the outer element includes me in their shadowDOM then things fall apart.
If you are creating a component that does NOT use ShadowDOM that you may still need to add your CSS into a shadowRoot. If someone else places your component into their shadowDOM, then you must add your CSS to their shadowRoot. You can do this with the following code:
const myStyle = document.createElement('style');
myStyle.setAttribute('component', 'my-el');
myStyle.textContent = ` button {
padding: 8px 20px;
}
.happy-btn {
background-color: pink;
}
.sad-btn {
background-color: #007;
color: white;
}`;
function addCss(el, selector, styleEl) {
// Check to see if we have been placed into a shadow root.
// If we have then add our CSS into that shadow root.
let doc;
try {
doc = el.getRootNode();
if (doc === document) {
doc = document.head;
}
}
catch(_ex) { doc = document.head; } // Shadow DOM isn't supported.
if (!doc.querySelector(selector)) {
doc.appendChild(styleEl.cloneNode(true));
}
}
class MyEl extends HTMLElement {
constructor() {
super();
addCss(this, 'style[component="my-el"]', myStyle);
}
connectedCallback() {
this.innerHTML = `<div class="spaced"><button class="happy-btn">I'm Happy</button></div>
<div class="spaced"><button class="sad-btn">I'm Sad</button></div>`;
}
}
customElements.define('my-el', MyEl);
class TheirEl extends HTMLElement {
constructor() {
super();
this.attachShadow({mode:'open'});
this.shadowRoot.innerHTML = `<hr/><my-el></my-el><hr/><my-el></my-el><hr/>`;
}
}
customElements.define('their-el', TheirEl);
<their-el></their-el>
The function addCss will place your CSS into the correct shadowRoot, or into document.head if there is no shadowRoot.
You must call addCss within your constructor to place the CSS in the correct location. This routine will also make sure you don't add it twice as long as you have a unique selector to identify your <style> tag.
In mine you see the <style> tag adds an attribute called component with a value of the component name. In my case component="my-el".
Then I use the selector 'style[component="my-el"]' to see if that tag is already in the shadowRoot, or document.head if there is no shadowRoot, and only add the styles if it does not already exist.
You can not assume that your component will not be in shadow DOM just because you are not using it. Use the example above to protect yourself.
Side Note
If you are using shadow DOM then this problem goes away since your have to place your CSS into your own shadowRoot.

Styling a child of a shadow root in Shadow DOM

I'm trying to style a child element of a shadow DOM root.
This defines a custom element called element-el, which have a span class-named 'x' with the letter x in it, which I want, for the state of the matter, to be red.
class El extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode:'open'});
shadow.innerHTML = '<span class="x">X</span>';
}
}
customElements.define ('element-el',El);
I've tried those CSS styles:
element-el::slotted(.x) {
color:red;
}
element-el::host .x {
color:red;
}
element-el:host .x {
color:red;
}
element-el::shadow .x {
color:red;
}
element-el /deep/ .x {
color: red;
}
element-el::content .x {
color:red;
}
The X does not become red.
I'm using Chrome 56, which is supposed to support this...
I want to style it without putting a style element inside the shadow DOM.
Here is a codepen:
http://codepen.io/anon/pen/OpRLVG?editors=1111
EDIT:
This article suggests that it is possible to style shadow children from an external CSS file -- are they simply wrong?
Apparently the problem is the fact that you are trying to use the global CSS to style the shadow tree elements.
You can use the :host pseudo selector, however in order to do so you will have to place the style inside the shadow tree content.
Make the following changes in your javascript:
class El extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode:'open'});
var innerHTML = '';
innerHTML += '<style>';
innerHTML += ':host(element-el.red) span {color: red}';
innerHTML += ':host(element-el.green) span {color: green}';
innerHTML += ':host(element-el.blue) span {color: blue}';
innerHTML += '</style>';
innerHTML += '<span class="x">X</span>';
shadow.innerHTML = innerHTML;
}
}
customElements.define ('element-el',El);
Check a functional example in your updated codepen.
A simple solution is to define the x class in the Shadow DOM:
class El extends HTMLElement {
constructor() {
super()
this.attachShadow({mode:'open'})
.innerHTML = `
<style>
.x { color: red }
</style>
<span class="x">X</span>`
}
}
customElements.define ('element-el',El)
<element-el></element-el>
Note: Because of Shadow DOM's style encapsulation, you'll always need to put a <style> element in the Shadow DOM, whether using Romulo's :host solution, a direct class declaration (see above), or an external stylesheet.
Of course if you use an inherited CSS property (like color) that will aplly to all your shadow DOM content, you can just use normal CSS:
class El extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode:'open'});
shadow.innerHTML = '<span class="x">X</span>';
}
}
customElements.define ('element-el',El);
element-el {
color: red;
}
<element-el></element-el>
This may help someone else in a similar situation.
In my case I have:
<svg class="icon">
<use xlink:href="#my-icon"></use>
</svg>
and to access the rendered icon in #shadow-root, I use:
.icon use {
fill: #f80;
}

Add a State property to an Inline Style in React

I have a react element that has an inline style like this: (Shortened version)
<div className='progress-bar'
role='progressbar'
style={{width: '30%'}}>
</div>
I want to replace the width with a property from my state, although I'm not quite sure how to do it.
I tried:
<div className='progress-bar'
role='progressbar'
style={{{width: this.state.percentage}}}>
</div>
Is this even possible?
You can do it like this
style={ { width: `${ this.state.percentage }%` } }
Example
yes its possible check below
class App extends React.Component {
constructor(props){
super(props)
this.state = {
width:30; //default
};
}
render(){
//when state changes the width changes
const style = {
width: this.state.width
}
return(
<div>
//when button is clicked the style value of width increases
<button onClick={() => this.setState({width + 1})}></button>
<div className='progress-bar'
role='progressbar'
style={style}>
</div>
</div>
);
}
:-)

Dynamically updating css in Angular 2

Let's say I have an Angular2 Component
//home.component.ts
import { Component } from 'angular2/core';
#Component({
selector: "home",
templateUrl: "app/components/templates/home.component.html",
styleUrls: ["app/components/styles/home.component.css"]
})
export class HomeComponent {
public width: Number;
public height: Number;
}
The template html file for this component
//home.component.html
<div class="home-component">Some stuff in this div</div>
And finally the css file for this component
//home.component.css
.home-component{
background-color: red;
width: 50px;
height: 50px;
}
As you can see there are 2 properties in the class, width and height. I would like the css styles for width and height to match the values of the width and height properties and when the properties update, the width and height of the div update. What is the proper way to accomplish this?
Try this
<div class="home-component"
[style.width.px]="width"
[style.height.px]="height">Some stuff in this div</div>
[Updated]:
To set in % use
[style.height.%]="height">Some stuff in this div</div>
To use % instead of px or em with #Gaurav's answer, it's just
<div class="home-component" [style.width.%]="80" [style.height.%]="95">
Some stuff in this div</div>
This should do it:
<div class="home-component"
[style.width]="width + 'px'"
[style.height]="height + 'px'">Some stuff in this div</div>
You can also use hostbinding:
import { HostBinding } from '#angular/core';
export class HomeComponent {
#HostBinding('style.width') width: Number;
#HostBinding('style.height') height: Number;
}
Now when you change the width or height property from within the HomeComponent, this should affect the style attributes.
If you want to set width dynamically with variable than use [] braces instead {{}}:
<div [style.width.px]="[widthVal]" [style.height.px]="[heightVal]"></div>
<div [style.width.%]="[widthVal]" [style.height.%]="[heightVal]"></div>
The accepted answer is not incorrect.
For grouped styles one can also use the ngStyle directive.
<some-element [ngStyle]="{'font-style': styleExpression, 'font-weight': 12}">...</some-element>
The official docs are here
Check working Demo here
import {Component,bind} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {FORM_DIRECTIVES} from 'angular2/form';
import {Directive, ElementRef, Renderer, Input,ViewChild,AfterViewInit} from 'angular2/core';
#Component({
selector: 'my-app',
template: `
<style>
.myStyle{
width:200px;
height:100px;
border:1px solid;
margin-top:20px;
background:gray;
text-align:center;
}
</style>
<div [class.myStyle]="my" [style.background-color]="randomColor" [style.width]="width+'px'" [style.height]="height+'px'"> my width={{width}} & height={{height}}</div>
`,
directives: []
})
export class AppComponent {
my:boolean=true;
width:number=200px;
height:number=100px;
randomColor;
randomNumber;
intervalId;
textArray = [
'blue',
'green',
'yellow',
'orange',
'pink'
];
constructor()
{
this.start();
}
start()
{
this.randomNumber = Math.floor(Math.random()*this.textArray.length);
this.randomColor=this.textArray[this.randomNumber];
console.log('start' + this.randomNumber);
this.intervalId = setInterval(()=>{
this.width=this.width+20;
this.height=this.height+10;
console.log(this.width +" "+ this.height)
if(this.width==300)
{
this.stop();
}
}, 1000);
}
stop()
{
console.log('stop');
clearInterval(this.intervalId);
this.width=200;
this.height=100;
this.start();
}
}
bootstrap(AppComponent, []);
You can dynamically change the style(width and height) of div by attaching dynamic value to inline [style.width] and [style.hiegh] property of div.
In your case you can bind width and height property of HomeComponent class with the div's inline style width and height property like this...
As directed by Sasxa
<div class="home-component"
[style.width]="width + 'px'"
[style.height]="height + 'px'">Some stuff in this div
</div>
For the working demo take a look at this plunker(http://plnkr.co/edit/cUbbo2?p=preview)
//our root app component
import {Component} from 'angular2/core';
import {FORM_DIRECTIVES,FormBuilder,AbstractControl,ControlGroup,} from "angular2/common";
#Component({
selector: 'home',
providers: [],
template: `
<div class="home-component" [style.width]="width+'px'" [style.height]="height+'px'">Some this div</div>
<br/>
<form [ngFormModel]="testForm">
width:<input type="number" [ngFormControl]="txtWidth"/> <br>
Height:<input type="number"[ngFormControl]="txtHeight" />
</form>
`,
styles:[`
.home-component{
background-color: red;
width: 50px;
height: 50px;
}
`],
directives: [FORM_DIRECTIVES]
})
export class App {
testForm:ControlGroup;
public width: Number;
public height: Number;
public txtWidth:AbstractControl;
public txtHeight:AbstractControl;
constructor(private _fb:FormBuilder) {
this.testForm=_fb.group({
'txtWidth':['50'],
'txtHeight':['50']
});
this.txtWidth=this.testForm.controls['txtWidth'];
this.txtHeight=this.testForm.controls['txtHeight'];
this.txtWidth.valueChanges.subscribe(val=>this.width=val);
this.txtHeight.valueChanges.subscribe(val=>this.height =val);
}
}
All the above answers are great. But if you were trying to find a solution that won't change the html files below is helpful
ngAfterViewChecked(){
this.renderer.setElementStyle(targetItem.nativeElement, 'height', textHeight+"px");
}
You can import renderer from import {Renderer} from '#angular/core';
I liked the look of WenhaoWuI's idea above, but I needed to identify the div with class .ui-tree in the PrimeNG tree component to set the height dynamically.
All the answers I could find required the div to be named (ie #treediv) to enable the use of #ViewChild(), #ViewChildren(), #ContentChild(), #ContentChilden() etc. This was messy with a third party component.
I finally found a snippet from Günter Zöchbauer :
ngAfterViewInit() {
this.elRef.nativeElement.querySelector('.myClass');
}
This made it easy:
#Input() height: number;
treeheight: number = 400; //default value
constructor(private renderer: Renderer2, private elRef: ElementRef) { }
ngOnInit() {
this.loading = true;
if (this.height != null) {
this.treeheight = this.height;
}
}
ngAfterViewInit() {
this.renderer.setStyle(this.elRef.nativeElement.querySelector('.ui-tree'), 'height', this.treeheight + "px");
}
you can achive this by calling a function also
<div [style.width.px]="getCustomeWidth()"></div>
getCustomeWidth() {
//do what ever you want here
return customeWidth;
}
In Html:
<div [style.maxHeight]="maxHeightForScrollContainer + 'px'">
</div>
In Ts
this.maxHeightForScrollContainer = 200 //your custom maxheight
My solution was a combination of the #ViewChild() and ngAfterViewInit() function.
HTML:
<div
class="form-field text-form-field"
#textInputContainer
>
<label *ngIf="field?.label"> My Label </label>
<input type="text"/>
</div>
TS:
#ViewChild("textInputContainer") textInputContainer;
constructor() {}
ngOnInit(): void {}
ngAfterViewInit(): void {
this.textInputContainer.nativeElement.style.width = "50%";
}

Resources