HostBinding not binding to CSS variable - css

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 🌟🌟

Related

How to mix pseudo class valid/invalid with Angular form control validator?

I'm using an Agular form control to control a "select" validity.
When said "select" is invalid, class "ng-invalid" can be found on the "select".
Class "ng-valid" is, when "select" is valid.
However, pseudo class remains ":valid" either way.
The problem is I'm using a third party library for style which is based on pseudo classes to handle style.
Take a look at this example,
https://stackblitz.com/edit/angular-xypbcc
I'd like that the pseudo class :invalid apply (and it's css style), when class is "ng-invalid", when select is empty.
(I know I could add the required to the select element, but I actually have other validators in my real use case)
Thanks
the easy way is copy the .css of inactive to .ng-invalid
A work-around is use setCustomValidity You can use a directive
#Directive({
selector: "[invalid]"
})
export class InvalidDirective implements OnInit, OnDestroy {
subscription$: any;
#Input("invalid") checkAtFirst: boolean = false;
constructor(private control: NgControl, private el: ElementRef) {}
ngOnInit() {
this.subscription$ = this.control.statusChanges.subscribe(res => {
this.el.nativeElement.setCustomValidity(res == "INVALID" ? "error" : "");
});
if (this.checkAtFirst && this.control.invalid)
this.el.nativeElement.setCustomValidity("error");
}
ngOnDestroy() {
this.subscription$.unsubscribe();
}
}
The directive inject in the constructor the ngControl (the input) and the elementRef (the HTMLElement) and subscribe to statusChange. I use the input if you want to check at first
So you use like
<select invalid=true formControlName="fcselect">
//or
<select invalid formControlName="fcselect">
You can see the example in the stackblitz

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.

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

angular testbed, query by css, find the pseudo element

I am writhing Angular 2+ unit test with TestBed.
Scenario, I want to verify my component, that the color of a pseudo element.
component.ts
label::before {
right: 0;
background-color: red;
}
#Component({
selector: 'app-test',
template: `
<div><label>a label</label></div>
`,
styleUrls: ['./test.component.scss'],
})
export class TestComponent {
}
so when I write unit test, I want to verify the pseudo element background color
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should set background color', () => {
const ele = fixture.debugElement.query(By.css('label::before')).nativeElement; // error here
// not sure how to use by.css to locate on the pseudo element
expect(ele.backgroundColor).toBe('....');
});
I would suggest writing your test in a different manner.
Fixture is of type ComponentFixture<T> where T is the component you are trying to access. The debugElement property has two properties that you are normally interested In when writing a test componentInstance and nativeElement
ComponentInstance is your component ts file. It's your class declaration in a sense.
NativeElement as the name suggests is the mark-up or your template
I don't think it's possible to do it the way you suggested.
However you could try
const color = window.getComputedStyle(fixture.debugElement.nativeElement.querySelector('label'), ':after').getPropertyValue('background-color');
This will give you a rgb result so for red it would be rgb(255,0,0)
I got this from: How to get pseudo element?
Try this and see if it works. It's not great that we had to access the window element inside of our test but it might solve your issue. Possibly create a better test without having to access the window api i would suggest.

Resources