How to dynamically generate CSS class and/or set its property - css

The title is not really a question it is more like an idea, I don't know what approach is best for my situation.
So, the problem. I have some 3rd party component that have some complex structure and styling. Some part of it has some predefined CSS class that I can override with CSS in my surrounding component. Something like this:
my component:
<div class="my-cmp-container">
<some-3rd-party-cmp></some-3rd-party-cmp>
</div>
3rd party component:
<div class="3rd-party-css-class">
...
</div>
For example, 3rd-party-css-class has style background-color: #f00, I can override it with .my-cmp-container .3rd-party-css-class { background-color: #fff; } etc. But. What if I need to set color dynamically, it's stored in a DB for example and I can't predefine each case in my class' CSS. I just have the color in hex.
In theory I can generate unique string to set as CSS class for every instance of some-3rd-party-cmp and somehow generate CSS in my component? I'm lost a little, what is the best approach for this?
Edit: Code sample to illustrate the situation https://stackblitz.com/edit/angular-kxdatq

What you are trying to do is the subject of this open issue about stylesheet binding in Angular. Until that feature is available, you can get what you want with a custom directive. Here is a directive that retrieves the checkbox element generated by ng-zorro-antd and applies two color attributes to it. The two colors are #Input properties and the directive implements OnChanges which allows to react to property binding changes.
#Directive({
selector: "[nz-checkbox][nz-chk-style]"
})
export class CheckBoxStyleDirective implements OnInit, OnChanges {
#Input("nz-chk-bkgnd") chkBkgndColor: string;
#Input("nz-chk-border") chkBorderColor: string;
private checkbox: HTMLElement;
constructor(private renderer: Renderer2, private el: ElementRef) { }
ngOnInit() {
this.checkbox = this.el.nativeElement.querySelector(".ant-checkbox-inner");
this.updateBackgroundColor();
this.updateBorderColor();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.chkBkgndColor) {
this.updateBackgroundColor();
}
if (changes.chkBorderColor) {
this.updateBorderColor();
}
}
updateBackgroundColor() {
if (this.checkbox) {
this.renderer.setStyle(this.checkbox, "background-color", this.chkBkgndColor);
}
}
updateBorderColor() {
if (this.checkbox) {
this.renderer.setStyle(this.checkbox, "border-color", this.chkBorderColor);
}
}
}
Once the directive attribute selector nz-chk-style is applied to the 3rd party element, you can set the checkbox background and border colors with property binding as follows:
<span nz-checkbox nz-chk-style [nz-chk-bkgnd]="bkgndColor" [nz-chk-border]="borderColor" >
See this interactive stackblitz for a demo.

Not sure if you are using Angular but you tagged it, so I guess you are.
If you want to change only the color and nothing more, instead of having a .3rd-party-css-class class, you could just have your with an ng-style like so:
<some-3rd-party-cmp ng-style="{ color: your_color_hex_variable }"></some-3rd-party-cmp>
You can also define a whole object if styles and pass it.
You can also use ng-class and pass one or an array of class names what you want to put additionally on your component:
<some-3rd-party-cmp ng-class="[cls1, cls2, cls3]"></some-3rd-party-cmp>
<some-3rd-party-cmp ng-class="[3rd-party-css-class, someCondition ? 'another-class-name' : '']"></some-3rd-party-cmp>
In the classes you can define the css rules you want to apply and thats it.
With this solutions you can avoid having extra wrapper elements for styling purposes which is a nice thing.

Related

Vaadin-flow: Css stylesheet import for custom components with shadow root element

I created a server-side component with a shadow-root element.. Is it possible to import a style sheet for the elements within that shadow-root? The CssImport annotation does not work, and I couldn't find anything similar, that could work?!
I could create a static String and add an element, but a css-file-import would be better?! (and of course I could use the component without a shadow-root, but the question was "is it possible" ... )
MyCustomComponent.java
#Tag("my-custom-component")
#CssImport("./components/my-custom-component.css")
public class MyCustomComponent extends Component {
public MyCustomComponent() {
super();
ShadowRoot shadow = getElement().attachShadow();
Span span = new Span();
span.getElement().setAttribute("part", "caption");
Div div = new Div();
div.getElement().setAttribute("part", "content");
shadow.appendChild(span.getElement());
shadow.appendChild(div.getElement());
}
}
my-custom-component.css
:host [part='caption'] {
background-color: red;
}
:host [part='content'] {
background-color: blue;
}
I'm curious why you would want a shadow root around a Flow component, as it doesn't really provide any benefits other than CSS encapsulation.
The #CssImport annotation with the themeFor parameter won't help you in this case, as that only works with Web Components using ThemableMixin (https://github.com/vaadin/vaadin-themable-mixin/).
I'm not sure whether it's possible to load css into a shadow root with Flow, but as long as you have part attributes on all elements you want to style, you can do that with a regular (non-shadow-dom) stylesheet, like so:
my-custom-component::part(caption) {
color: red;
}
Just put that in your styles.css or wherever you have your app's normal global css.

How can I style :host element dynamically based on parameter from #Input?

Usually, I can style the very root of my component by using the :host pseudo style like this.
:host{ border: 1px solid gold; }
But how shold I handle if said style is supposed to be set dynamically, based on the parameters passed to #Input?
The only way I can think of at the moment is to add an auxilliary DIV and style it like so.
<div [ngClass]="styleMeDynamically"> ... </div>
Is there a way to apply a style dynamically directly on the host without the injected DIV?
I've found this suggestion but it requires explicitly stating the classes and connecting them to separate inputs. I'd like to get a config object as passed in parameter and bind the styling using [ngClass] to retail full flexibility.
Probably #HostBinding decorator can help you. It allows to bind any host attribute including class and style. For example:
#Component({ ... })
export class MyComponent {
// you can conditionally add a class to the host element
#Input()
#HostBinding('class.large')
large = false;
// it's possible to bind a style as well
#Input()
#HostBinding('style.border.px')
borderWidth = 1;
#Input()
green = false;
// and you can use a getter
#HostBinding('style.border-color')
get borderColorStyle() {
return this.green ? 'green' : 'black';
}
}
Since angular 9 it should be possible even to bind a CSS variable, see Improved CSS class and style binding section of the 9 version release article.
<div [style.--main-border-color]=" '#CCC' ">
<p style="border: 1px solid var(--main-border-color)">hi</p>
</div>
What you can do is,
Create a custom directive that will accept a style object. and inside that directive, you can get the reference of host element and modify its style.
Here is a Demo
And here is a quick explanation.
Create a directive as which will accept a style object.
import {Directive,TemplateRef,ElementRef,OnChanges,SimpleChanges,OnInit,Renderer2,DoCheck,Input} from "#angular/core";
#Directive({
selector: "[appSetStyle]"
})
export class SetStyleDirective implements OnInit, OnChanges {
#Input() appSetStyle: { [key: string]: any } = {};
constructor(private elementRef: ElementRef<HTMLElement>) {}
ngOnInit(): void {}
ngOnChanges(changes: SimpleChanges): void {
this.applyStyles();
}
applyStyles(): void {
if (this.appSetStyle) {
for (const key in this.appSetStyle) {
this.elementRef.nativeElement.style[key] = this.appSetStyle[key];
}
}
}
}
Use that style object with any html element or any other component in your project.
<app-header [appSetStyle]="dynamicStyles"></app-header>
If you don't want to make a directive then you can inject the ElementRef inside the component itself which you want to style.
ElementRef is the what you need to use to get the reference of host.
I hope this will help.

HIghlighting row during runtime

I am trying to highlight a row based user input. I am using Angular 5, with ag-grid-angular 19.1.2. Setting the style with gridOptions.getRowStyle changes the background, but I would rather use scss classes if possible. The function setHighlight() is called in the html file through (change)=setHighlight()
setHighlight() {
const nextChronoId = this.getNextChronoDateId();
// this.highlightWithStyle(nextChronoId); // Working solution
this.highlightWithClass(nextChronoId);
const row = this.gridApi.getDisplayedRowAtIndex(nextChronoId);
this.gridApi.redrawRows({ rowNodes: [row]})
}
Function definitions:
highlightWithStyle(id: number) {
this.gridApi.gridCore.gridOptions.getRowStyle = function(params) {
if (params.data.Id === id) {
return { background: 'green' }
}
}
}
highlightWithClass(id: number) {
this.gridApi.gridCore.gridOptions.getRowClass = function(params) {
if (params.data.Id === id) {
return 'highlighted'
}
}
}
My scss class:
/deep/ .ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last, .highlighted{
background-color: green;
}
My issue
Using getRowClass does not apply my highlighted class correctly to the rowNode. After reading (and trying) this, I think that my custom scss class overwritten by the ag-classes. The same problem occurs when using rowClassRules.
Question
How can I make Angular 5 and ag-grid work together in setting my custom scss class correctly?
Stepping with the debugger shows the class is picked up and appended to the native ag-grid classes.
In rowComp.js:
Addition, screen dump from dev tools:
angular's ViewEncapsulationis the culprit here.
First be aware that all shadow piercing selectors like /deep/ or ::ng-deep are or will be deprecated.
this leaves, to my knowledge, two options.
use ViewEncapsulation.None
add your highlighted class to the global stylesheet
setting ViewEncapsulation.None brings its own possible problems:
All components styles would become globally available styles.
I would advise to go with option two.
this answers sums it up pretty well.
Additionally:
.ag-theme-balham .ag-row .ag-row-no-focus .ag-row-even .ag-row-level0 .ag-row-last
this selector will never match anything, you should change it to
.ag-theme-balham .ag-row.ag-row-no-focus.ag-row-even.ag-row-level0.ag-row-last
every class after ag-theme-balham exists on the same element.
with the selector you wrote, you would denote a hierarchy.
Hope this helps

Is it possible to apply dynamic style as string in Angular?

I have a <span> that I want to apply dynamic style to.
Style is stored in a css-like string variable and can be arbitrary e.g.
myStyle = 'color: white; font-weight: bold;'
or
myStyle = 'background-color: red;'
I expected it to work like
<span style="{{myStyle}}">
but it didn't.
I tried different options but none seem to work for me for different reasons:
I can't put styles in a .css file and use class names because style is coming from server in the form of aforementioned string
Using [style.color] etc. doesn't suit me because I don't know what the style can be
Using [ngStyle] doesn't suit me because it expects object like {'color': 'red', 'font-weight': 'bold'} and I only have string
The reason I have a style stored in a string is because I need to apply it in HTML generated on the server where I simply pass that string to a placeholder variable in a velocity template.
I am almost confident that it can't be done the way I want but probably I am overlooking some solution.
All you need is DomSanitizer and bypassSecurityTrustStyle
Component side :
import { DomSanitizer } from '#angular/platform-browser';
constructor(private doms : DomSanitizer) {}
newStyle = 'background-color:red';
safeCss(style) {
return this.doms.bypassSecurityTrustStyle(style);
}
Template side :
<p [style]="safeCss(this.newStyle)">
Start editing to see some magic happen :)
</p>
WORKING DEMO
Angular provides the DomSanitizer service which can convert strings into style objects. I think this is exactly your case.
constructor(private sanitizer: DomSanitizer) {
}
sanitizeStyle() {
return this.sanitizer.bypassSecurityTrustStyle('background-color: red');
}
<span [style]="sanitizeStyle()">
I think I will go the way of converting the incoming css string into a style object and then applying it to <span> using [ngStyle]

Applying dynamic styling to injected HTML in Angular 2

For an Angular project I'm working on, I'm injecting HTML into a <div> like so:
<div class="myClass" [innerHTML]="htmlToInsert"></div>
The htmlToInsert contains a variety of things, notably <a> tags. Previously we were styling all these tags like so:
.myClass ::ng-deep a {
color: #f00;
text-decoration: none;
}
And this worked fine. But now I need the color of these links to be dynamically generated during component initialization, based on data coming in from elsewhere. All of the dynamic styling I've seen in Angular requires you to apply things directly to the HTML tag, but we don't have them here to work with.
How can I apply dynamic styling to HTML that is also dynamically generated? Can I directly change the CSS class somehow? Would using a pipe be the correct approach here? Is there another method I don't know about? I could maybe refactor code if there is absolutely no other way of doing this.
So if you can't modify the innerHTML you are passing in, you can achieve this functionality with a custom directive. Essentially you would tag your div that contains your innerHTML with a custom directive. That directive then looks for any anchor tags in it and changes the color based on an input.
// component.html
<div anchorColor [color]="dynamicColor" [innerHTML]="htmlToInsert"></div>
// directive.ts
#Directive({selector: '[anchorColor]'})
export class AnchorColorDirective implements OnAfterViewInit {
#Input() color: string;
constructor(private el: ElementRef){
}
// afterViewInit lifecycle hook runs after DOM is rendered
ngAfterViewInit(){
// get anchor element
let anchorEl = this.el.nativeElement.querySelector('a');
// assign color
if(anchorEl){
anchorEl.style.color = this.color;
}
}
}
Here is a working plunkr https://plnkr.co/edit/QSYWSeJaoUflP94Cy4Hm?p=preview

Resources