-
Notifications
You must be signed in to change notification settings - Fork 25.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Need a way for a component to add CSS classes to host non-destructively #7289
Comments
👍 |
Any updates on this? |
Isn't this quite easily solved by: @HostBinding('class') @Input('class') classList: string = ''; This will capture any previous classes that have been manually assigned to the components dom. However does not facilitate for ones set dynamically by e.g. directives. |
My workaround.
|
Currently, I use 3 methods to add the class name for a component. I don't know whether they are good way to do it, but it works according to my test. @component({
@HostBinding('class.m-grid__item') public bindStyle: boolean = true;
|
@xmeng1 Thanks for your summary. Just a small correction for approach 1 to avoid confusion (for anyone who might read this). It has to be |
@MartinMa you are right, thanks! |
I used a manually provided const prefix = 'ant-btn'
@Directive({
selector: 'button[antBtn]',
providers: [ NgClass ],
})
export class HelloComponent implements OnChanges {
@Input() color: string = 'default'
private hostClasses: { [name: string]: boolean }
constructor(@Self() private ngClass: NgClass) { }
ngOnChanges(changes: SimpleChanges): void {
this.hostClasses = {
[`${prefix}`]: true,
[`${prefix}-${this.color}`]: true,
}
this.updateHostClasses()
}
private updateHostClasses(): void {
this.ngClass.ngClass = this.hostClasses
this.ngClass.ngDoCheck()
}
} More details can be found in the StackOverflow answer. |
Overriding the native import { Component, Input, HostBinding } from '@angular/core';
@Component({
selector: 'gist-keeps-class',
template: 'this component keepes class="class" attributes'
})
export class KeepsClass {
@Input() booleanInput: boolean = false;
@Input() stringInput: 'some-thing' | 'another-thing' | 'a-third-thing' = 'another-thing';
@Input() class: string = ''; // override the standard class attr with a new one.
@HostBinding('class')
get hostClasses(): string {
return [
this.class, // include our new one
this.booleanInput ? 'has-boolean' : '',
this.stringInput
].join(' ');
}
} <gist-keeps-class
class="some classes"
[booleanInput]="true"
[stringInput]="some-thing"
></gist-keeps-class> will output this: <gist-keeps-class class="some classes has-boolean some-thing" >
this component keepes class="class" attributes
</gist-keeps-class> It's not throughly tested, but should work? |
We sometimes get this issue's question (teaching Angular Boot Camp) from people first learning Angular, before they are really committed to it. We manage to get them up and running with variants of the solutions written above, but is definitely an area where we are to some extent "apologizing" that this is currently quite a rough edge. My request for whoever designs a full solution to this: aim for best in class, very easy and smooth. |
@arniebradfo do you think that it is possible to wrap your solution in an abstract component or in a directive ? |
An observation I made. If you have two directives (or a component and a directive) that do the above way using Input and HostBinding it will not work. Only one wins. Will build a small example repo this week to illustrate what I mean |
My solution:
usage:
output:
|
Based on the answer by @trotyl I implemented a simple solution: https://stackblitz.com/edit/angular-cvkrpe?file=src%2Fapp%2Fapp.component.html This solution will not overwrite class attribute on host, [class.binding] or [ngClass] (as other solutions might do). import { Directive } from '@angular/core';
import { NgClass } from '@angular/common';
@Directive({})
export class HostClass extends NgClass {
apply(value: string | string[] | Set<string> | { [klass: string]: any }) {
this.ngClass = value;
this.ngDoCheck();
}
} Usage: @Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
providers: [HostClass]
})
export class HelloComponent implement OnInit {
@Input() name: string;
constructor(@Self() private hostClass: HostClass) { }
ngOnInit() {
if (this.name === 'special name') {
this.hostClass.apply('special);
} else {
this.hostClass.apply({
[this.name]: true,
normal: true,
});
}
} |
How about using an ElementRef to do this? We ran into the same issue and saw that Material uses this method to add classes. This works for both class and ngClass classnames set on the host element. constructor(elementRef: ElementRef) {
elementRef.nativeElement.classList.add('my-classname');
} |
@mslooten - this is what I had to use as well. But it looks bulkier than |
This worked well for me and was a simple solution: @HostBinding('class.header-logo') public additionalClass: boolean = true; |
All examples
I've just add |
Wow another common use case issue lingering since 2016. Perhaps my open source software expectations are too high? |
@etay2000 It has already been fixed in Ivy for some time, just wan't brought back to view engine. |
Ok thanks, I appreciate the info. |
I'm having an issue with I am currently using @faizalmy reply here in this issue
|
this issue has been fixed in angular 9.0.0 anyone who meet this issue can try the below codes in angular 9 import { Component } from "@angular/core";
@Component({
selector: "app-root",
template: '<button (click)="updateHostClassList()">host</button>',
host: {
"[class]": "hostClass"
}
})
export class AppComponent {
hostClass = {};
updateHostClassList() {
const random = Math.floor(Math.random() * 1000).toString(36);
this.hostClass = {
[random]: true
};
}
} |
I confirm this works in Angular 9.0.0 import { Component, OnInit, HostBinding } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
@HostBinding('class') private hostClass: object = {};
public ngOnInit(): void {
this.addClass('myYellowBackground');
}
private addClass(className: string): void {
this.hostClass[className] = true;
}
private removeClass(className: string): void {
delete this.hostClass[className];
}
} Then, if you add a class to that component as follows... <app-root class="myRedBorder"></app-root> ... both myRedBorder and myYellowBackground classes will be applied to the instance of |
I think this is some easy way for adding class to component:
And then use component like this:
@vladimiry you can add ass much classes as you need like this:
and then several inline classes too:
|
@wnvko adding one class is easy, the issue is about host-bind a multiple classes. |
This has been resolved as of #34938 Closing the issue. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Say a user wants to add margin around a component:
But the component itself has need to set a dynamic CSS class to the host element:
Doing this in the component will completely overwrite the
class
property, wiping away the user's classes.In other situations,
NgClass
serves this purpose, but a component cannot apply a directive to its host element. Using the[class.whatever]
syntax also doesn't solve this, as thewhatever
is the variable part.Proposal
Each component can, conceptually, have two distinct
class
lists: one that is from the user and one that comes from the component itself. These could be resolved independently with Angular rendering the union. Any time the user usesngClass
or the[class.whatever]
syntax, it should override whatever is set to "class".cc @mhevery
The text was updated successfully, but these errors were encountered: