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

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.

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.

Is there some way with which i can apply css dynamically to my child component?

I have a component which is reusable. This component is called from parent component multiply times and sometimes the background page of the parent component is white and sometimes is black.
My child component generates form tags dynamycally - inputs,selects, textarea.
That means i can't have fixed styles in my css in my component for my content.
So when when the background page is white - i have one style for my inputs - for example black background. When the background page is black i have another style for my inputs - for example white bacgrkound.
To solve this is issue:
i tried
Adding input property in my child component ts file
#Input()
public cssTemplate;
in html
<div [ngClass]="{'form-group-white-bg': cssTemplate == 'white', 'form-group-dark-bg': cssTemplate == 'black'}">
<label for=""></label>
....
In the CHILD component i am sending value for input property depending on where the child component is called
if it is called on page with white background
<app-form-group cssTemplate="black" formControlName="address">
</app-form-group>
if it is called on black bacgrkound
<app-form-group cssTemplate="white" formControlName="address" [data]="{ field: 'address', label: 'Address' }">
</app-form-group>
but the problem here is that sometimes on my parent component this component is called multiply times
on one page can be called 12 times where i need 10 inputs and 2 selects
on other page can be called 15 times etc.
That means that i need to repat my self 15 times
<app-form-group cssTemplate="white" formControlName="address">
</app-form-group>
<app-form-group cssTemplate="white" formControlName="someItherControlName">
</app-form-group>
and everywhere to put cssTemplate="white".
ngFor is not an optin because this child component is called multiply times but not on same place in the HTML structure in the parent.
How can i solve this DRY?
you can add styles in your styles.css (the styles general for all the application). If e.g. you has
.white h1{
color:red;
}
.black h1{
color:green;
}
You can use [ngClass] in the "parent"
<div [ngClass]="toogle?'white':'black'">
<hello name="{{ name }}"></hello>
</div>
<button (click)="toogle=!toogle">toogle</button>
See [stackblitz][1]
NOTE: I used the way [ngClass]="expresion" (where expresion use conditional operator) better that [ngClass]="{'classname1':condition;'classname2':!condition}"
Update about your comment "how can i prevent repeating my self on child call", really I don't understand so much. I don't know if you want to make a directive like, e.g.
#Directive({
selector: 'hello', //<--the selector is the selector of the component
exportAs: 'helloDiv'
})
export class HelloDirective implements OnInit{
constructor(#Self() private component:HelloComponent,private dataService:DataService){
}
ngOnInit(){
console.log(this.component.name)
this.dataService.theme.subscribe(res=>{
this.component.theme=res;
})
}
}
This allow to "extends" the component -in the stackblitz the variable "theme" change-
[1]: https://stackblitz.com/edit/angular-ivy-sjwxyq?file=src%2Fapp%2Fapp.component.html
You can use an input property to create a css class map to pass on to ngClass. This object should be an object of string arrays.
It can be pretty much as complex and contain as many classes and rules as you need it too
#Input() color: 'white' | 'red' | 'hotpink' = 'white';
classMap: any;
ngOnInit() {
this.updateClassMap();
}
updateClassMap() {
this.classMap = {
[this.color]: !!this.color, // Add this class if not null
};
}
Then in the Html simply pass it to ngClass
<div [ngClass]="classMap">
Styling Child Components depending on Parent Component
There are two approaches I commonly take in this scenario
:ng-deep - create a style rule based on a class which is set in your parent component
utilize #ContentChildren() to set a property directly on your child components and call detectChanges() manually after the change.
To adopt the first solution you need to exercise greater care in your css naming rules, as using ng-deep obviously breaks the isolation of those style rules.
To adopt the second approach needs some considering due to it technically circumventing the standard input/output flow in Angular and thus can be a bit of a surprise "undocumented behavior" for any other maintainers of the application.
I'm a bit on the fence whether I prefer one approach over the other. The first approach seems more trivial to me, but it can also cause unintended style rule overwrites, while the second approach involves a lot more scripting and seems a bit of a hack.
Approach 1: ng-deep
Give your parent component an input and update a class on a block-element wrapping your <ng-content>.
create your desired style rules in your child component.
// parent component
#Component(...)
export class FooParent {
#Input() bgStyle: 'light' | 'dark' = 'light';
}
<!-- parent component template -->
<div class="parent" [ngClass]="{light: bgStyle == 'light', dark: bgStyle == 'dark'}">
<ng-content></ng-content>
</div>
// child.css
::ng-deep .light .child-container {
background-color: lightblue;
}
::ng-deep .dark .child-container {
background-color: royalblue;
}
My targeted element in the example is .child-container, you would write a similar style rule for each element you want to affect.
Approach 2: Using ContentChildren to pass along a value
Add a #ContentChildren() decorator to your parent component which selects for your child components.
inject a ChangeDetectorRef
implement ngAfterViewInit to loop through each child and set the value
call detectChanges() once done.
add the ngClass directive as normally in your child component.
Parent
#Component({
selector: 'parent',
templateUrl: 'parent.component.html',
styleUrls: ['parent.component.scss']
})
export class ParentComponent implements AfterViewInit, OnChanges {
#Input() bgStyle: 'light' | 'dark' = 'light';
#ContentChildren(ChildComponent) childComponents!: QueryList<ChildComponent>;
constructor(private change: ChangeDetectorRef) {
}
ngOnChanges(changes: SimpleChanges) {
if ('bgStyle' in changes) {
this.updateChildComponents();
}
}
updateChildComponents() {
this.childComponents.forEach(child => {
child.bgStyle = this.bgStyle;
});
this.change.detectChanges();
}
ngAfterViewInit() {
this.updateChildComponents();
}
}
<!-- parent.component.html -->
<ng-content></ng-content>
Child
#Component({
selector: 'child',
templateUrl: 'child.component.html',
styleUrls: ['child.component.scss']
})
export class ChildComponent implements OnInit {
bgStyle: 'light' | 'dark' = 'light';
constructor() {
}
ngOnInit(): void {
}
}
<!-- child.component.html -->
<div [ngClass]="{light: bgStyle == 'light', dark: bgStyle == 'dark'}" class="child-container"></div>
// child.component.css - you would apply styles as you needed obviously.
.child-container {
width: 40px;
height: 40px;
margin: .5rem;
}
.light.child-container {
background-color: lightblue;
}
.dark.child-container {
background-color: royalblue;
}
Usage
<!-- any other template -->
<parent>
<child></child>
<child></child>
<child></child>
</parent>
Note: If you are creating the ChildComponent directly in the ParentComponent's own template you need to use #ViewChildren instead of #ContentChildren

HostBinding not binding to CSS variable

I am trying to bind a variable in my angular component to a variable inside a CSS keyframe that I am using to animate a div dynamically. I came across HostBinding as a potential solution however I (think) I followed the declaration correctly but the animation does not work when the variables are used. I am using angular 10.0.14 any help is appreciated.
This is my css code:
#keyframes swap{
0%{background-color:red; left: var(--inX);}
100%{background-color: red; left: var(--finX);}
}
And here this is my component.ts HostBinding declaration:
#Component({
selector: 'app-',
templateUrl: './component.html',
styleUrls: ['./component.css'],
})
export class Component implements OnInit {
#HostBinding('style.--inX')
private inX: string = '100px';
#HostBinding('style.--finX')
private finX:string = '200px';
}
there is now wrong with the css but the hosting change the name of the custom css variable from --inX to --in-x.
so to solve just change the custom css varable name to single word or --in-x
})
export class AppComponent {
name = "Angular " + VERSION.major;
#HostBinding("style.--start")
private inX: string = "50px";
#HostBinding("style.--end")
private finX: string = "200px";
}
stackblitz demo 🚀🚀
in case you want to use the camelCase style for the variables names you need set the style directly as an object but I still recommend the previous solution because it easy to update a single property directly.
#HostBinding("style") private style = {
'--intX':'100px',
'--finX':'200px'
}
stackblitz demo 🌟🌟

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.

Angular 6: How to change page background-color dynamically

I work on an Angular 6 app (with Bootstrap 4) and need to change the page background color depending on which page the user enters. Default is white, but for login and registration screen the page color needs to be blue.
What I found so far:
in ngAfterViewInit() using
this.elementRef.nativeElement.ownerDocument: this approach makes the
app more vulnerable to XSS attacks and I want to avoid that.
Set View Encapsulation to None in app.component.ts: this way I can
set the body color in the app.component, that is 1 step forward.
So, now I have in my app.component.css:
body {
background-color: blue;
}
Question:
How can I change that color value (in app.component) using a variable?
With [ngStyle] I can not reach the body background-color.
Maybe using a css variable? but how can I change the value of that css variable dynamically?
I'm new to Sass, but might this offer a solution?
My question is different from the other question on this subject as I need to be able tochange the color value dynamically.
use render2 and set class to body using document object
app.component.ts
constructor(private renderer: Renderer2) {
this.renderer.addClass(document.body, 'body-class');
}
Note: if you are toggling classes, just remove previous class before assigning new class
The way I would do it is based on the routes. When defining the routes, you can add extra data, for example a class name.
When the route changes (i.e. via navigation), the data from the active route can be used to set the class on the body tag.
This is how you can achieve this
Update the styles.css to add different classes for body:
body {
...
}
body.red {
background-color: #ff8181;
}
body.blue {
background-color: #a0c3ee;
}
Update the routes to add extra data, specifying the body class name. Add an extra data property, like bodyClass:
const routes: Routes = [
{ path: '', component: DefaultComponent },
{ path: 'red', component: RedComponent, data: { bodyClass: 'red' } },
{ path: 'blue', component: BlueComponent, data: { bodyClass: 'blue' } }
];
Write the code to read the bodyClass and set the class to the body element when navigation occurs. This can be done in the app.component.ts:
#Component({
selector: 'app-root',
template: `
<div>
<router-outlet></router-outlet>
<app-menu></app-menu>
</div>
`
})
export class AppComponent implements OnInit {
constructor(
#Inject(DOCUMENT) private document,
private renderer: Renderer2,
private router: Router,
private activatedRoute: ActivatedRoute) {
}
ngOnInit() {
this.router.events
.pipe(filter((event) => event instanceof NavigationEnd))
.pipe(map(() => this.activatedRoute))
.pipe(map((route) => {
while (route.firstChild) {
route = route.firstChild;
}
return route;
}))
.pipe(filter((route) => route.outlet === 'primary'))
.pipe(mergeMap((route) => route.data))
.subscribe((event) => this.updateBodyClass(event.bodyClass));
}
private updateBodyClass(customBodyClass?: string) {
this.renderer.setAttribute(this.document?.body, 'class', '');
if (customBodyClass) {
this.renderer.addClass(this.document?.body, customBodyClass);
}
}
}
Here is a demo on StackBlitz: https://stackblitz.com/edit/angular-ivy-rs1tai
Why not just define a separate class based on different background-color? For instance:
.blue {
background: blue
}
.green {
background: green
}
.grey {
background: grey
}
And then set these classes on the body using ng-class or ngClass whatever convention you use based on the page. This should be fairly easy to implement.
My favourite approach for doing stuff like this is to add a class to html tag depending on the route. For example, we have some code in our basic layout component (you could put it in your root component) that does this inside ngOnInit:
let wrapper = ''
const path = this.activatedRoute.snapshot.routeConfig.path
wrapper += this.tidyPath(path)
if (wrapper !== '') wrapper += '-'
const childPath = this.activatedRoute.snapshot.firstChild.routeConfig.path
wrapper += this.tidyPath(childPath)
this.routeWrapperCssClass = wrapper
$('html').addClass(this.routeWrapperCssClass)
This will add a class to your html tag to make it look like this (although you may have to tweak this code to suit your app):
<html class="registration">
....
</html>
The class will be instantly updated whenever you change route.
Now you can do this in your main stylesheet:
body {
background-color: pink;
}
html.registration body {
background-color: yellow;
}
You could also do things like hide elements based on the class added to the html tag like so:
.navbar {
display: block;
}
html.registration .navbar {
display: none;
}
Because you know what route you are on at all times you have total control via CSS.
PS you may want to use render2 instead of jQuery to do the DOM manipulation - see this article ... https://alligator.io/angular/using-renderer2 ... never used it myself before but almost identical to jQuery syntax - thanks to Pratap A.K answer for this
Personally i replace :
<body>
<app-root></app-root>
</body>
to
<app-root></app-root>
and then i add all the time the body on components or if i have multi
router-outlets i add it on app.component.css

Resources