Facing Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked - html

I am trying to assign a random color to the div background using the below example
Random Color
But facing below error:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has
changed after it was checked.
You can check-in console of created stackblitz.
I have already tried the below answers:
How to manage Angular2 "expression has changed after it was checked" exception when a component property depends on current datetime
Expression ___ has changed after it was checked
But no luck! Can anyone please look into this and help?

One solution for this is to use a directive.
So I created a directive called appRandomColor
Here's the code for it.
import {Directive, ElementRef, Input, OnInit} from '#angular/core';
#Directive({
selector: '[appRandomColor]'
})
export class RandomColorDirective implements OnInit {
constructor(private element: ElementRef) { }
ngOnInit() {
this.element.nativeElement.style.color = this.getRandomColor();
}
getRandomColor() {
var color = Math.floor(0x1000000 * Math.random()).toString(16);
return '#' + ('000000' + color).slice(-6);
}
}
And added it to declarations in AppModule
Then I applied it to the *ngFor loop. And no errors.
<ul>
<li class="hero" *ngFor="let hero of heroes" appRandomColor>
{{ hero }}
</li>
</ul>
I suggest reading more about Angular Change Detection because it will help you understand these errors more.
Here are some articles that I find very helpful
https://indepth.dev/a-gentle-introduction-into-change-detection-in-angular/
https://indepth.dev/everything-you-need-to-know-about-change-detection-in-angular/
Edit
On Component.ts
colorsArray = ['#FF5733', '#DA4323', '#FFB1A0', '#BB523C', '#BB2505', '#DE4922'];
On Component.html
<li class="hero" *ngFor="let hero of heroes" [appRandomColor]="colorsArray">
{{ hero }}
</li>
To add predefined colors array to directive
#Input('appRandomColor') colors: string[];
ngOnInit() {
this.element.nativeElement.style.color = colors[Math.floor(Math.random() * colors.length)];
}

Related

Why ngfor directive is not working, though I have created proper object in Typescript class

I have created proper Ingredient object in Typesript, also proper "model" for the object. Though ngfor directive is not working. I am getting this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" on inspecting in browser.
My model code
export class Ingredient {
constructor(public name: string, public amount: number){}
}
My TypeScript code
import { Component, OnInit } from '#angular/core';
import { Ingredient } from '../shared/ingredient.model';
#Component({
selector: 'app-shopping-list',
templateUrl: './shopping-list.component.html',
styleUrls: ['./shopping-list.component.css']
})
export class ShoppingListComponent implements OnInit {
ingredients: Ingredient[] = [
new Ingredient ("Apple", 5),
new Ingredient ("Tomato", 5)
];
constructor() { }
ngOnInit(): void {
}
}
My HTML code
<div class="row">
<div class="col-xs-10">
<app-shopping-edit></app-shopping-edit>
<hr>
<ul class = "list-group">
<a class = "list-group-item"
style = "cursor : pointer"
*ngfor = "let ingredient of ingredients">
{{ ingredient.name }}({{ ingredient.amount}})
</a>
</ul>
</div>
</div>
Page is loading properly, but ingredients are not loading. On inspecting in browser this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" is coming.
Can someone please help.
Try moving the *ngFor inside the ul tag.
Edit: You have a typo.. It's *ngFor="", not *ngfor="".
If you are inside AppModule check if you have BroswerModule in the imports array there.
If you are in some different module then check if CommonModule is a part of that module's array. CommonModule provides us with core Angular features like *ngIf or *ngFor in your specific case.
Also watch out, you typed *ngfor instead *ngFor.

'trim' of undefined TypeError in Angular app when I don't call trim anywhere

I'm currently trying to learn Angular and as I'm working through a couple of ideas I had, I ran into the following error in the dev console of Chrome:
ERROR TypeError: Cannot read property 'trim' of undefined
at Function.addMultipleClasses (primeng-dom.js:19)
at ButtonDirective.createIconEl (primeng-button.js:59)
at ButtonDirective.setIconClass (primeng-button.js:78)
at ButtonDirective.set label [as label] (primeng-button.js:92)
at setInputsForProperty (core.js:10940)
at elementPropertyInternal (core.js:9984)
at ɵɵpropertyInterpolate1 (core.js:15551)
at Module.ɵɵpropertyInterpolate (core.js:15514)
at CmsComponent_Template (cms.component.html:12)
at executeTemplate (core.js:9579)
Here is my HTML:
<h1>Angular Router App</h1>
<!-- This nav gives you links to click, which tells the router which route to use (defined in the routes constant in AppRoutingModule) -->
<nav>
<ul>
<li><a routerLink="/login" routerLinkActive="active">/login</a></li>
<li><a routerLink="/" routerLinkActive="active">/</a></li>
</ul>
</nav>
<button type="button"
pButton
label="{{word}}"
(click)="buttonPress()">
</button>
<!-- The routed views render in the <router-outlet>-->
<router-outlet></router-outlet>
And here is my TS:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-landing',
templateUrl: './cms.component.html',
styleUrls: ['./cms.component.scss']
})
export class CmsComponent implements OnInit {
private onWord: number = 0;
private words: Array<string> = ["One","Two","Three","Two"];
public word: string = this.words[this.onWord];
constructor(
) { }
ngOnInit(): void {
}
public buttonPress(): void {
// Bumps the index
this.onWord++;
// Keeps the value in the proper range
if (this.onWord >= this.words.length) {
this.onWord = 0;
}
// Updates the word to the new index
this.word = this.words[this.onWord];
console.log("The button is now on " + this.word);
}
}
I'm using Angular and Typescript. Any thoughts on what's going wrong? The button functions as intended except for that error whenever I click it.
Thanks!
was the same problem, I found a solution on the official forum, in order to fix the error you need to add -> icon = "pi"
<button
icon="pi"
type="button"
pButton
[label]="(documentsCount$ | async)?.toString()"
></button>
This is not an error generated from your code but in the package you're using (in this case PrimeNg). The method seems to be expecting some input which it's not getting. Try passing '$event' in the click method and handle it in the ts file.
(click)="buttonPress($event)"
As an ideal implementation, use the predefined button element provided by PrimeNg.
Turns out having {{word}} as the button label broke things!
Apparently I can't have dynamically changing button labels...
Use p-button instead:
<p-button
label="{{word}}"
(onClick)="buttonPress()">
</p-button>
See the forum entry

How can I change a body tag class in Angular 10 (best practice)?

I want to switch between two classes (light and dark) at TAG Body.
What I did? I created a service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ThemeService {
body = document.body;
constructor() { }
changeLight() {
this.body.classList.replace('light', 'dark');
}
changeDark() {
this.body.classList.replace('dark', 'light');
}
}
It is working as expected but I know that this code does not use best practices.
What is the correct way to change between these two classes?
Edit: Added a service to the stackblitz, but again, there are many ways to do this. This is just a starting point.
While the "right way" is subjective, you have some options to make it "Angular-y"
Component:
import { Component, Inject } from '#angular/core';
import { DOCUMENT } from '#angular/common';
// Create a type that accepts either the string 'light' or 'dark' only
type Theme = 'light' | 'dark';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
// Default to 'light' theme
currentTheme: Theme = 'light';
// Inject document which is safe when used with server-side rendering
constructor(#Inject(DOCUMENT) private document: Document) {
// Add the current (light) theme as a default
this.document.body.classList.add(this.currentTheme);
}
// Swap them out, and keep track of the new theme
switchTheme(newTheme: Theme): void {
this.document.body.classList.replace(this.currentTheme, newTheme)
this.currentTheme = newTheme;
}
}
HTML:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
</p>
Many ways to do this, but one benefit of defining the types is if you provide a bad value, such as:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
<button (click)="switchTheme('noop')">Invalid</button>
</p>
You'll get an error:
Argument of type '"noop"' is not assignable to parameter of type 'Theme'.
StackBlitz

Host Binding rearranges applied classes

I am using #HostBinding('class') to inject classes into the host element. The classes to be injected are generated based on user-supplied parameters. The problem I ran into and I could not find anyone else experiencing is that the classes are applied in an order different from the way I expected them.
For example, having a component defined below:
import {Component, HostBinding, Input} from '#angular/core';
#Component({
selector: '[icon]',
template: `
<ng-content></ng-content>
`
})
export class SuiIconComponent {
#Input() iconType = '';
#HostBinding('class')
get classes(): string {
return [this.iconType, 'icon'].join((' '));
}
}
When I apply the component like shown below:
<div icon iconType="car"></div>
And inspect, I see <div class="icon car"></div> instead of the appropriately formatted <div class="car icon"></div>.
I have tried reversing the array before joining but that did not help either.
Is there any way I get the classes to get rendered in the proper order?
Edit: I realized the classes are being rearranged in alphabetic order.
I'm not sure why angular changes the order, but you can solve your problem with little bit of change in your template.
#Component({
selector: 'icon',
template: `
<div [ngClass]="iconType + ' icon'">
<ng-content></ng-content>
</div>
`
})
export class SuiIconComponent {
#Input() iconType = '';
}
and use it as follows
<icon iconType="car">
Some content here
</icon>

Angular interpolate inside a component like mat-checkbox

So I want to have a mat-checkbox component with a HTML string inside the label.
I tried the following:
<mat-checkbox class="check">
{{ someHtml }}
</mat-checkbox>
But it prints the HTML string as a string and doesn't render it.
Using the following doesn't work either:
<mat-checkbox class="check" [innerHtml]="someHtml">
</mat-checkbox>
This just replaces the whole content, including the checkbox that gets generated at runtime. Is there any way to inject the html into the label?
You could use Angular Directives
The idea here is to fetch the element from the HTML, then append some raw HTML dynamically.
Supose this scenario
app.component.html
<mat-checkbox class="check" [appendHtml]="innerHtml"></mat-checkbox>
app.component.ts
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
innerHtml = `<div style="border: 1px solid red;"> Text inside </div>`;
constructor() {}
}
As you can see, I added a appendHtml attribute to the mat-checkbox element. This is a custom directive that expects a string as "raw" HTML.
append-html.directive.ts
#Directive({
selector: '[appendHtml]'
})
export class AppendHtmlDirective implements AfterViewInit {
#Input('appendHtml') html: string
constructor(private element: ElementRef) {
}
ngAfterViewInit() {
const d = this.element.nativeElement.querySelector('label');
d.insertAdjacentHTML('beforeend', this.html);
}
}
The AppendHtmlDirective expects an html property of type string and implements AfterViewInit interface (from Angular) to fetch the element once it is rendered. By injection, Angular provides us the element which is being applied; so, the ElementRef from the constructor is our MatCheckbox element, in that case.
We can use the insertAdjacentHTML function to append childs to the element. I just fetched the label element from the MatCheckbox to fit inside of it. In every case, you should see where to append the HTML.
I mean, label here works, bc MatCheckbox has a tag whitin matching that. If you want to reuse this Directive for other elements, you should be passing the literal to find inside.
i.e.:
append-hmtl.directive.ts
// ...
#Input() innerSelector: string
// ...
ngAfterViewInit() {
const d = this.element.nativeElement.querySelector(this.innerSelector);
d.insertAdjacentHTML('beforeend', this.html);
}
app.component.hmtl
<mat-checkbox class="check" [appendHtml]="innerHtml" innerSelector="label"></mat-checkbox>
Moreover, you can pass as many inputs as you need to customize the styling or behavior of your directive.
Cheers
I think you should just wrap everything in a div and put it on the outside.
<div>
<mat-checkbox class="check"> </mat-checkbox>
{{ someHtml }}
</div>