Create css class for whole document with angular - css

In my CSS file, I have a class:
.test{
background: red;
}
But at the beginning of my app, I'd like to redefine this class based on the server response such that the background becomes blue or green depending on a variable.
It is very important to attribute to this class (.test) the new color as many of my elements have already this class and I don't want to apply a new class to them.
Not sure it's very clear but to summarize, I want to create a class from javascript (using angular 2) that will apply to the whole document.

The code below will find any style rules (including those inside media rules) that are part of the document, and overwrite any styles that are matched by the selector.
You can call modifyStyles('.test', { 'background': 'blue' }) on an instance of the service to make all styles with the .test class to have a blue background. You probably want to play with the way the selector functions, because in its current implementation any rule that has .test anywhere within it will have its background changed. You might prefer changing the regex to ^.test$ so that it matches .test and only .test.
#Injectable()
export class CssUpdateService {
constructor( #Inject(DOCUMENT) private document: Document) {
}
modifyStyles(selector: string, styles: any) {
const rulesToUpdate = this.findRules(new RegExp(`\b${selector}\b`, 'g'));
for (let rule of rulesToUpdate) {
for (let key in styles) {
rule.style[key] = styles[key];
}
}
}
/**
* Finds all style rules that match the regular expression
*/
private findRules(re: RegExp) {
let foundRules: CSSStyleRule[] = [];
let ruleListToCheck = Array.prototype.slice.call(this.document.styleSheets);
for (let sheet of ruleListToCheck) {
for (let rule of (<any[]>(sheet.cssRules || sheet.rules || []))) {
if (rule instanceof CSSStyleRule) {
if (re.test(rule.selectorText)) {
foundRules.push(rule);
}
}
else if (rule instanceof CSSMediaRule) {
ruleListToCheck.push(rule);
}
}
}
return foundRules;
}
}

EDIT (bc I was confused on your requirements initially) -
I don't think there's a good way to modify the global styles file after the application loads, but if I am wrong on that someone please correct me.
The shadow DOM makes this tricky. I would provide a runtime configuration variable to your module and then conditionally add a class to your application's root component.
<div class="outer-app-wrapper" [ngClass]="someValue">
Then in your global styles.css file, you can just define all the different variations of .test there could be.
.someValue1 .test {
background: red;
}
.someValue2 .test {
background: green;
}
.someValue3 .test {
background: yellow;
}
I think if you define all the variations in the styles.css file, you should be able to avoid having to use the 'host-context:' selector in the descendant components. There's no need to add any class to an element outside of Angular's scope like the 'body' element, just add it to the top-most element of your app, and as long as descendant components don't redefine the test class as it is defined in the global stylesheet, it should work fine.
Note - you could also use #HostBinding to add the classes to your root component if you don't want to add a wrapper element or modify an existing one

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 to dynamically generate CSS class and/or set its property

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.

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

BEM: adding modifier to already existed modifier

I'd been confused by simple scenario when I was working with BEM.
There is a base button in example:
.button {
// styles for button
}
and its modifier with more specific styles:
.button.button_run {
// additional styles for this type of button
// i.e. custom width and height
}
One moment I realize that I need modifier for button_run, let's name it like button_run_pressed:
.button_run_pressed {
// more styles, i.e. darker background color
}
The problem is that it's not correct to name the last element as I did above button_run_pressed according to BEM conventions. But I need to add "pressed" styles only to "run" button, not for all buttons by writing class like button_pressed and mixing modifier button button_run button_pressed.
How should I refactor my code to match BEM conventions?
According to http://getbem.com/naming/, the modifier classes are initiated with two hyphens (--). So a modifier for .button should look like
.button--modifier { /* ... */ }
In your case, I would suggest choosing the following names:
.button {}
.button--run {}
.button--run-pressed {}
Notice, that I also decoupled the modifier classes from the block class, which is more according to BEM rules. You want to avoid creating classes which depend on others to work.
Since you added less as a tag to the post, here's how this could look in less or scss:
.button {
// button styles
&--run {
// modified styles
}
&--run-pressed {
// more modifiers
}
}
This would result in the classnames I wrote above
Firstly, the name should be .block--modifier or .button--run
If you want it only works with both modifier run and press, you should name it as
.button.button--run.button--pressed
Hope this help

Control CSS variable from Angular 5

Is there any way to control a CSS variable defined at a component's root level using Angular methods? In JavaScript, we have document.documentElement.style.setProperty() when we set at root level.
In angular, can we use ':host' to declare css variable for global access within component? or do we need to use something like '::ng-deep :root'?
The following question also remains unanswered:
Angular: Use Renderer 2 to Add CSS Variable
Yes you can set variables in root scope:
:root {
--main-color: red
}
Yes you can use :host selector to target element in which the component is hosted.
:host {
display: block;
border: 1px solid black;
}
You can also use, :host-context to target any ancestor of the component. The :host-context() selector looks for a CSS class in any ancestor of the component host element, up to the document root.
:host-context(.theme-light) h2 {
background-color: #eef;
}
Note: ::ng-deep or /deep/ or >>> has been deprecated.
Read more about it here: special css selectors in angular
Just an additional information.
It works both inside ':root' as well as ':host'
We can set values to them by:
constructor(private elementRef: ElementRef) { }
then
this.elementRef.nativeElement.style.setProperty('--color', 'red');
The most constructive and modular way to use css vars in components (with viewEncapsulation) is as such:
// global css
:root {
--main-color: red
--alt-color: blue
}
// inside component component css
::ng-deep :root {
--specific-css-var: var(--main-color)
}
:host {
background-color: var(--specific-css-var)
}
:host(.conditional-class) {
--specific-css-var: var(--alt-color)
}
NOTE: despite ::ng-deep being deprecated, it hasn't been replaced yet (and has no replacement), as can be read in several discussion like this
Best thing for each component like a background color with out using ::ng-deep (which sets bg for all children)
import the following
import {ElementRef} from '#angular/core';
declare elementref in constructor
constructor(private elementRef: ElementRef) {}
then call the function ngAfterViewInit()
ngAfterViewInit(){
this.elementRef.nativeElement.ownerDocument.body.style.backgroundColor = ' white';
}
this sets bg to white but you can replace it with a hex color as well, so you can do that with every component

Resources