Angular: conditional class with *ngClass - javascript

What is wrong with my Angular code? I am getting the following error:
Cannot read property 'remove' of undefined at BrowserDomAdapter.removeClass
<ol>
<li *ngClass="{active: step==='step1'}" (click)="step='step1'">Step1</li>
<li *ngClass="{active: step==='step2'}" (click)="step='step2'">Step2</li>
<li *ngClass="{active: step==='step3'}" (click)="step='step3'">Step3</li>
</ol>

Angular version 2+ provides several ways to add classes conditionally:
type one
[class.my_class] = "step === 'step1'"
type two
[ngClass]="{'my_class': step === 'step1'}"
and multiple option:
[ngClass]="{'my_class': step === 'step1', 'my_class2' : step === 'step2' }"
type three
[ngClass]="{1 : 'my_class1', 2 : 'my_class2', 3 : 'my_class4'}[step]"
type four
[ngClass]="step == 'step1' ? 'my_class1' : 'my_class2'"
You can find these examples on the documentation page

[ngClass]=... instead of *ngClass.
* is only for the shorthand syntax for structural directives where you can for example use
<div *ngFor="let item of items">{{item}}</div>
instead of the longer equivalent version
<template ngFor let-item [ngForOf]="items">
<div>{{item}}</div>
</template>
See also https://angular.io/docs/ts/latest/api/common/index/NgClass-directive.html
<some-element [ngClass]="'first second'">...</some-element>
<some-element [ngClass]="['first', 'second']">...</some-element>
<some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
<some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
<some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
See also https://angular.io/docs/ts/latest/guide/template-syntax.html
<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
[class.special]="!isSpecial">This one is not so special</div>
<!-- reset/override all class names with a binding -->
<div class="bad curly special"
[class]="badCurly">Bad curly</div>

Another solution would be using [class.active].
Example :
<ol class="breadcrumb">
<li [class.active]="step=='step1'" (click)="step='step1'">Step1</li>
</ol>

That's the normal structure for ngClass is:
[ngClass]="{'classname' : condition}"
So in your case, just use it like this...
<ol class="breadcrumb">
<li [ngClass]="{'active': step==='step1'}" (click)="step='step1'">Step1</li>
<li [ngClass]="{'active': step==='step2'}" (click)="step='step2'">Step2</li>
<li [ngClass]="{'active': step==='step3'}" (click)="step='step3'">Step3</li>
</ol>

with the following examples you can use 'IF ELSE'
<p class="{{condition ? 'checkedClass' : 'uncheckedClass'}}">
<p [ngClass]="condition ? 'checkedClass' : 'uncheckedClass'">
<p [ngClass]="[condition ? 'checkedClass' : 'uncheckedClass']">

You can use ngClass to apply the class name both conditionally and not in Angular
For Example
[ngClass]="'someClass'">
Conditional
[ngClass]="{'someClass': property1.isValid}">
Multiple Condition
[ngClass]="{'someClass': property1.isValid && property2.isValid}">
Method expression
[ngClass]="getSomeClass()"
This method will inside of your component
getSomeClass(){
const isValid=this.property1 && this.property2;
return {someClass1:isValid , someClass2:isValid};
}

Angular provides multiple ways to add classes conditionally:
First way
active is your class name
[class.active]="step === 'step1'"
Second way
active is your class name
[ngClass]="{'active': step=='step1'}"
Third way
by using ternary operator class1 and class2 is your class name
[ngClass]="(step=='step1')?'class1':'class2'"

You should use something ([ngClass] instead of *ngClass) like that:
<ol class="breadcrumb">
<li [ngClass]="{active: step==='step1'}" (click)="step='step1; '">Step1</li>
(...)

In Angular 7.X
The CSS classes are updated as follows, depending on the type of the expression evaluation:
string - the CSS classes listed in the string (space delimited) are added
Array - the CSS classes declared as Array elements are added
Object - keys are CSS classes that get added when the expression given in the value evaluates to a truthy value, otherwise they are removed.
<some-element [ngClass]="'first second'">...</some-element>
<some-element [ngClass]="['first', 'second']">...</some-element>
<some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
<some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
<some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>

Additionally, you can add with method function:
In HTML
<div [ngClass]="setClasses()">...</div>
In component.ts
// Set Dynamic Classes
setClasses() {
let classes = {
constantClass: true,
'conditional-class': this.item.id === 1
}
return classes;
}

to extend MostafaMashayekhi his answer for option two>
you can also chain multiple options with a ','
[ngClass]="{'my-class': step=='step1', 'my-class2':step=='step2' }"
Also *ngIf can be used in some of these situations usually combined with a *ngFor
class="mats p" *ngIf="mat=='painted'"

You can use [ngClass] or [class.classname], both will work the same.
[class.my-class]="step==='step1'"
OR
[ngClass]="{'my-class': step=='step1'}"
Both will work the same!

While I was creating a reactive form, I had to assign 2 types of class on the button. This is how I did it:
<button type="submit" class="btn" [ngClass]="(formGroup.valid)?'btn-info':''"
[disabled]="!formGroup.valid">Sign in</button>
When the form is valid, button has btn and btn-class (from bootstrap), otherwise just btn class.

We can make class dynamic by using following syntax. In Angular 2 plus, you can do this in various ways:
[ngClass]="{'active': arrayData.length && arrayData[0]?.booleanProperty}"
[ngClass]="{'active': step}"
[ngClass]="step== 'step1'?'active':''"
[ngClass]="step? 'active' : ''"

Let, YourCondition is your condition or a boolean property, then do like this
[class.yourClass]="YourCondition"

The directive operates in three different ways, depending on which of three types the expression evaluates to:
If the expression evaluates to a string, the string should be one or more space-delimited class names.
If the expression evaluates to an object, then for each key-value pair of the object with a truthy value the corresponding key is used as a class name.
If the expression evaluates to an array, each element of the array should either be a string as in type 1 or an object as in type 2. This means that you can mix strings and objects together in an array to give you more control over what CSS classes appear. See the code below for an example of this.
[class.class_one] = "step === 'step1'"
[ngClass]="{'class_one': step === 'step1'}"
For multiple options:
[ngClass]="{'class_one': step === 'step1', 'class_two' : step === 'step2' }"
[ngClass]="{1 : 'class_one', 2 : 'class_two', 3 : 'class_three'}[step]"
[ngClass]="step == 'step1' ? 'class_one' : 'class_two'"

ngClass syntax:
[ngClass]="{'classname' : conditionFlag}"
You can use like this:
<ol class="breadcrumb">
<li [ngClass]="{'active': step==='step1'}" (click)="step='step1'">Step1</li>
<li [ngClass]="{'active': step==='step2'}" (click)="step='step2'">Step2</li>
<li [ngClass]="{'active': step==='step3'}" (click)="step='step3'">Step3</li>
</ol>

This is what worked for me:
[ngClass]="{'active': dashboardComponent.selected_menu == 'profile'}"

For elseif statement (less comparison) use like that: (For example you compare three statement)
<div [ngClass]="step === 'step1' ? 'class1' : (step === 'step2' ? 'class2' : 'class3')"> {{step}} </div>

Not relevant with [ngClass] directive but I was also getting the same error as
Cannot read property 'remove' of undefined at...
and I thought to be the error in my [ngClass] condition but it turned out the property I was trying to access in the condition of [ngClass] was not initialized.
Like I had this in my typescript file
element: {type: string};
and In my [ngClass] I was using
[ngClass]="{'active', element.type === 'active'}"
and I was getting the error
Cannot read property 'type' of undefined at...
and the solution was to fix my property to
element: {type: string} = {type: 'active'};
Hope it helps somebody who is trying to match a condition of a property in [ngClass]

<div class="collapse in " [ngClass]="(active_tab=='assignservice' || active_tab=='manage')?'show':''" id="collapseExampleOrganization" aria-expanded="true" style="">
<ul> <li class="nav-item" [ngClass]="{'active': active_tab=='manage'}">
<a routerLink="/main/organization/manage" (click)="activemenu('manage')"> <i class="la la-building-o"></i>
<p>Manage</p></a></li>
<li class="nav-item" [ngClass]="{'active': active_tab=='assignservice'}"><a routerLink="/main/organization/assignservice" (click)="activemenu('assignservice')"><i class="la la-user"></i><p>Add organization</p></a></li>
</ul></div>
Code is good example of ngClass if else condition.
[ngClass]="(active_tab=='assignservice' || active_tab=='manage')?'show':''"
[ngClass]="{'active': active_tab=='assignservice'}"

Try Like this..
Define your class with ''
<ol class="breadcrumb">
<li *ngClass="{'active': step==='step1'}" (click)="step='step1; '">Step1</li>
<li *ngClass="{'active': step==='step2'}" (click)="step='step2'">Step2</li>
<li *ngClass="{'active': step==='step3'}" (click)="step='step3'">Step3</li>
</ol>

The example is a bit big, but triggering a class instead of typing inline is my first preferred approach.
this way you can add as many possibilities as you want to your element.
There may be a way for those who want to bind more than one [ngClass] to a single element.
<span class="inline-flex items-center font-medium" [ngClass]="addClass">{{ badge.text }}</span>
import { ChangeDetectionStrategy, Component, Input } from '#angular/core';
type Badge = {
size?: 'basic' | 'large';
shape?: 'basic' | 'rounded';
color?: 'gray' | 'red' | 'yellow' | 'green' | 'blue' | 'indigo' | 'purple' | 'pink';
dot?: boolean;
removeButton?: false;
text?: string;
}
#Component({
selector: 'bio-badge',
templateUrl: './badge.component.html',
styleUrls: ['./badge.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BioBadgeComponent {
#Input() badge!: Badge;
get addClass() {
return {
'px-2.5 py-0.5 text-sx': this.badge.size === 'basic',
'px-3 py-0.5 text-sm': this.badge.size === 'large',
'rounded-full': this.badge.shape === 'basic',
'rounded': this.badge.shape === 'rounded',
'bg-gray-100 text-gray-800': this.badge.color === 'gray',
'bg-red-100 text-red-800': this.badge.color === 'red',
'bg-yellow-100 text-yellow-800': this.badge.color === 'yellow',
'bg-green-100 text-green-800': this.badge.color === 'green',
'bg-blue-100 text-blue-800': this.badge.color === 'blue',
'bg-indigo-100 text-indigo-800': this.badge.color === 'indigo',
'bg-purple-100 text-purple-800': this.badge.color === 'purple',
'bg-pink-100 text-pink-800': this.badge.color === 'pink',
}
}
}

If user want to display the class on basis of && and ||
then below one is work for me
[ngClass]="{'clasname_1': condition_1 && condition_2, 'classname_2': condition_1 && condition2, 'classname_3': condition}"
Example:
[ngClass]="{'approval-panel-mat-drawer-side-left': similar_toil_mode==='side' && showsTheSimilarToilsWithCloseIcon, 'approval-panel-mat-drawer-side-right': similar_toil_mode==='side' && !showsTheSimilarToilsWithCloseIcon, 'approval-panel-mat-drawer-over': similar_toil_mode==='over'}"

Related

How to add multiple classNames to nextjs elements

I have an unordered list element that looks like this:
<ul className={styles["projects-pd-subdetails-list"]}>
{detail.subdetails.map((sub) => (
<li
className={styles["projects-pd-text projects-pd-subdetail"]}
>
{sub}
</li>
))}
</ul>
With a normal React element, I would be able to apply the multiple classes for the li element like this:
<li className="projects-pd-text projects-pd-subdetail">{sub}</li>
However, having a space like I do in nextjs means the styles are just getting ignored. How can I fix this problem and properly account for two classNames for my li element here?
You can use multiple className like this
<li className={`${styles.projects-pd-text} ${styles.projects-pd-subdetail}`}>
{sub}
</li>
But there is a problem. It may throws an error(I guess, not sure). You may use camelCase in your css className.
<li className={`${styles.projectsPdText} ${styles.projectsPdSubdetail}`}>
{sub}
</li>
or, if you don't want to camelCase
<li className={`${styles["projects-pd-text"]} ${styles["projects-pd-subdetail"]}`}>
{sub}
</li>
Let me know if it works.
A simple array join should suffice.
["class1", "class2", "class3"].join(" ")
result: "class1 class2 class3"
<li className={[styles.projects_pd_text, styles.projects_pd_subdetail].join(" ")}>
{sub}
</li>
Or save it as a variable for multiple uses
const listClasses = [styles.projects_pd_text, styles.projects_pd_subdetail].join(" ")
As stated in my original comment I have not worked with Next.js.
It appears as though styles is a map of some kind i.e.:
const styles = {
"projects-pd-subdetails-list": "Class Name A",
"projects-pd-text": "Class Name B",
"projects-pd-subdetail": "Class Name C"
}
This means that by using a line similar to styles["projects-pd-text projects-pd-subdetail"] you are attempting to retrieve the value for the key "projects-pd-text projects-pd-subdetail" which does not exist.
I would suggest retrieving the values individually from the map and then joining them together with your choice of string concatenation.
className={styles["projects-pd-subdetail"] + " " + styles["projects-pd-text"]}
// OR
className={`${styles["projects-pd-subdetail"]} ${styles["projects-pd-text"]}`}
clsx is generally used to conditionally apply a given className.
https://www.npmjs.com/package/clsx
Because it might be tedious to always write styles.className for every class you need to add to an element, you can create a utility function that makes things look neater.
For example, in my case, I created this function:
export const classes = (styles: any, classes: string) => {
const list = classes.split(' ');
classes = '';
for (const className of list) {
classes += `${styles[className] }`
}
return classes;
}
in a util file.
And on elements, I can do this
<div className={classes( styles, 'hero-container text-success text-bold italicize another-class yet-another-class ')}>
Lorem ipsum dolor
</div>
A PLUS:
One other issue around classnames I encountered getting started with NextJS on VSCode was getting emmet tag generation to list the classnames the NextJS way when I type css class selector.
"Complete NextJS Classname Emmet": {
"scope": "javascript,typescript,typescriptreact,javascriptreact",
"prefix": ["."],
"body": [
"<div className={classes( styles, '$1')}>",
"\t$2",
"</div>"
],
"description": "Lean autocomplete emmet in nextjs css modules classname"
}
I added this to my VSCode > Preferences > User Snippets > typescriptreact.json
These made working with classnames in NextJS easier for me - especially on VSCode.
It worked for me.
<div className={styles.side +" "+ styles.side2}>side2</div>
Thanks to CodenNerd
If you console log your css or scss module class (ex. ImportedStyleModule.someClassName) you'll see it's just a string that has an auto generated UID concatenated.
Ergo, it's just a string so you can use a number of ways to join them like so:
//ts
const mergeStyles = (styleArray: string[]) => (styleArray.map((style: string) => `${style}`).join(" "));
//js
const mergeStyles = (styleArray) => (styleArray.map((style) => `${style}`).join(" "));
//example
return (
<span
onClick={() => setIsClicked(!isClicked)}
className={`${!!isClicked
? mergeStyles([NavbarStyle.navButton, NavbarStyle.navButtonOpen])
: NavbarStyle.navButton}`}
>
<Image src='/image-logo.svg' alt='logo' width='80px' height='40px' />
</span>
);
<div className={`${GloStyles.flexRow} ${MyStyles.gap_10rem}`}> Hello </div>
Explanation
className accept 'String' only
use 'String template' solved,
it is just like a normally react app className, nothing fancy
enter image description here
enter image description here
a Function would be much cleaner
const convertToNextClassName = (className) => className.split(' ').map(c => styles[c]).join(' ')
then in the jsx you can directly call it
<div className={convertToNextClassName("firstClass secondClass")}></div>
for conditional i suggest using classnames from npm
The array join() is the cleanest solution, as #rain mentioned here.
You can also use global css class names.
<span className={[styles.grid, "disabled"].join(" ")}>content</span>
This is how it works for me in my style component
<ul>
<li className={router.pathname == "/" && styles.active}><Link href="/">Home</Link></li>
<li className={router.pathname == "/about" && styles.active}><Link href="/about">About</Link></li>
<li className={router.pathname == "/contact" && styles.active}><Link href="/contact">Contact</Link></li>
<li><Link href="/404">404</Link></li>
</ul>

Angular Toggle class only on single div

data
test = [
{
"test1": {
"qq": ["qq", "ww", "aa", "bb"],
},
},
{
"test1": {
"qq": ["11", "22", "33", "44"],
},
}
];
code
<ng-container *ngFor="let list of test">
<div class="a" (click)="clickEvent(list)" [ngClass]="list.status ? 'blue' : ''">
{{list.test1.qq[0]}}
</div>
<div class="a" (click)="clickEvent(list)" [ngClass]="list.status ? 'blue' : ''">
{{list.test1.qq[1]}}
</div>
</ng-container>
ts
status: boolean = false;
clickEvent(list){
list.status = !list.status;
}
https://stackblitz.com/edit/angular-skdlf2?file=src%2Fapp%2Fapp.component.ts
Now I click 'qq', 'ww' will also toggle class
But I just want to toggle a class on single div.
Click 'qq', Only 'qq' toggle class
How to do it?
The *ngFor do not create multiple instances, you have actually only one instance. So if you change the status all your divs will change.
The solution is to create a childComponent, the *ngFor will iterate and create as many component instance as items.
With that, every component (item) will have it own status.
I hope this will help !
Depending on the structure of your array, you might need to pass the targeted item to the click handler. Which in your case list.test1.qq[1] or list.test1.qq[0].
Then you might need to separately tell your component which item is clicked, because the targeted item in your case is a string.
In my working solution, I've just defined a string called clicked which will memorize and toggle the targeted item and passed the targeted item to the click handler.
My Working Soulution on stackblitz
<div class= "container">
<li *ngFor="let list of test"></li>
<div class="a" (click)="clickEvent(list)" [ngClass]="list.status ? 'blue' : ''">
{{list.test1.qq[0]}}
</div>
<div class="a" (click)="clickEvent(list)" [ngClass]="list.status ? 'blue' : ''">
{{list.test1.qq[1]}}
</div>
</div>

How to add multiple conditions in ng-class using Angular.js

I need to add multiple conditions ng-class using Angular.js. I am explaining my code below.
<li ng-class="{'active':($root.stateName==='app.settings')}"><a ui-sref="app.settings">Settings</a></li>
Here I need to add another two conditions i.e-$root.stateName==='app.settings.area' and $root.stateName==='app.settings.area.manageState' with that existing conditions with OR condition.
Use an OR operator || in the evaluated expression:
<li ng-class="{'active':($root.stateName==='app.settings' || $root.stateName==='app.settings.area.manageState' )}">
<a ui-sref="app.settings">Settings</a>
</li>
I think this will also work
<li ng-class="{'active':($root.stateName==='app.settings' , $root.stateName==='app.settings.area.manageState' } ">
<a ui-sref="app.settings">Settings</a>
</li>
Try with
`<li ng-class="{'active':($root.stateName==='app.settings' || $root.stateName==='app.settings.area' || $root.stateName==='app.settings.area.manageState' || $root.stateName==='app.settings.area.manageCity' || $root.stateName==='app.settings.vechile' || || $root.stateName==='app.settings.vechile.category' || $root.stateName==='app.settings.vechile.manufacture' || $root.stateName==='app.settings.vechile.model')}">
<a ui-sref="app.settings.area.manageState">Settings</a>
</li>`
This is one of the blog which helps understanding all possible ways to use conditional ng-class
https://scotch.io/tutorials/the-many-ways-to-use-ngclass

How to add multiple condition in a single class using Angular.js

I need one help. I need to add multiple condition with AND operator in ng-class using Angular.js. I am explaining my code below.
<div class="mainaccordion_title-width" ng-click="manageCollapseExpand(place, false)">
<i ng-class="{'fa fa-minus-circle': place.expanded, 'fa fa-plus-circle': !place.expanded }"></i>
{{place.root_des}}
</div>
Here I am adding one condition but I need to add another condition (i.e-place.cstatus==1 and place.cstatus==0) for both class. Please help.
Use && instead of and.
(i.e-place.cstatus==1 && place.cstatus==0)
As commented before, you can also create a function that takes arguments and return classnames.
Benefit of this approach is, it will keep your view clean and the logic will stay in JS. This will also make it more configurable as you can write a code and share it across multiple elements.
function myCtrl($scope) {
$scope.place = {
expanded: false,
cstatus: 1,
root_des: 'test'
}
$scope.manageCollapseExpand = function(place, seleccted) {
place.expanded = !place.expanded
}
// assuming place should be an array
$scope.getClasses = function(index) {
var className = 'fa ';
if ($scope.place.expanded && $scope.place.cstatus === 1) {
className += 'fa-minus-circle '
} else {
className += 'fa-plus-circle ';
}
return className
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller='myCtrl'>
<div class="mainaccordion_title-width" ng-click="manageCollapseExpand(place, false)">
<i ng-class="getClasses(0)"></i>
{{place.root_des}}
<p>Classes: {{getClasses(0)}}</p>
</div>
</div>
Simply you can conbind condition using &&
<div class="mainaccordion_title-width" ng-click="manageCollapseExpand(place, false)">
<i ng-class="{'fa fa-minus-circle': place.expanded && place.cstatus==1, 'fa fa-plus-circle': !place.expanded && place.cstatus==0 }"></i>
{{place.root_des}}
</div>

How can I have several statements inside a structural directive on Angular2

I'm new to Angular 2 and haven't worked front end before. So I'm not sure how to write a template structural directives. I need to translate this from ejs/angular 1 to Angular 2
for (var i = 0; i < n; i++) {
<% var classes = item.columns[i].fields.skin.indexOf("sign-in") > -1 ? "sign-in" : "" %>
<div class="dropdown menu-standout desktop <%= classes %>">
<a><%= item.columns[i].fields.title %></a>
</div>
}
My best guess was this, but I'm sure I can't have curly brackets twice inside ngFor. Any help?
<li *ngFor="let column of item.columns">
{{var classes = column.fields.skin.indexOf("sign-in") > -1 ? "sign-in":""}}
<div class="dropdown menu-standout desktop" {{ classes }}>
<a>{{column.fields.title}}</a>
</div>
</li>
You can not have binding assignment in template interpolation directive.
So, it should be like this
{{column.fields.skin.indexOf("sign-in") > -1 ? "sign-in":""}}
You can not declared variable inside interpolation directive
Remove the var classes = part from template otherwise it will template parse error.
you want to add a class dynamically, so try this :
<li *ngFor="let column of item.columns">
<div [class.active] = "isActive = 'column.fields.skin.indexOf("sign-in") > -1 ? "sign-in":"" ' " class="dropdown menu-standout desktop">
<a>{{column.fields.title}}</a>
</div>
</li>
it is not good practice to have js code in your view.
in your component
export class MyComponent {
isActive: boolean = false;
}
I fixed the syntax by writing an extra function
<li *ngFor="let column of item[0].fields.columns">
<div class="dropdown menu-standout desktop {{ checkIndex(column) }}" >
<a>{{column.fields.title}}</a>
</div>
</li>
export class PageDropdownComponent {
constructor() {}
checkIndex(classes:any) {
return classes.fields.skin.indexOf("sign-in") > -1 ? "sign-in" : "";
}
Thanks everyone else for the help, it was useful for understanding the syntax

Categories

Resources