getting ExpressionChangedAfterItHasBeenCheckedError Angular 4 - javascript

Im aware similar questions exist but none of those have provided me with an answer that works..
Basically I have a site with some services that inject data dynamically
In my app.component.ts I have two headers.. one when your on the home page and one for when your on any other page
app.component.html
<app-header *ngIf="router.url !== '/'"></app-header>
<app-header-home *ngIf="router.url != '/'"></app-header-home>
<router-outlet></router-outlet>
<app-footer></app-footer>
app.component.ts
import { Component } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'app';
router: string;
constructor(
private _router: Router
) {
this.router = _router.url;
}
}
now I also have a service that dynamically injects the title of the header
headerTitle.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class HeaderTitleService {
title = new BehaviorSubject('');
constructor() { }
setTitle(title: any) {
this.title.next(title);
}
}
then In my home component for example I set the title
home.component.ts
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { HeaderTitleService } from '../../services/headerTitle.service';
import { HeaderImageService } from '../../services/headerImage.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor(
private headerTitleService: HeaderTitleService,
private headerImageService: HeaderImageService
) { }
ngOnInit() {
}
ngAfterViewInit() {
this.headerTitleService.setTitle(`
We strive to create things
<br> that are engaging, progressive
<br> & above all
<span class="highlight">
<em>innovative.</em>
</span>
`);
}
}
now basically it was all working until I put in the if statements on the two headers
now Im getting this error
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: '
We strive to create things
<br> that are engaging, progressive
<br> & above all
<span class="highlight">
<em>innovative.</em>
</span>
'.
not sure how I can fix this.. I tried setting the values in ngAfterViewInit but it did nothing
or does anyone know another way I could accomplish this??
Thanks

You can try using a setTimeOut method instead and set the values
inside of that
setTimeout(this.headerTitleService.setTitle(`
We strive to create things
<br> that are engaging, progressive
<br> & above all
<span class="highlight">
<em>innovative.</em>
</span>
`), 0);
note this is a work around and not a full proff solution to the problem .
To know why this error occurs in Angular change detection you need to know how the change detection works in Angular for this you can refer to this blog by Maxim NgWizard K

I know i fixed this in mine.
here is a great post
everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror
i have forced the change detection
export class AppComponent {
name = 'I am A component';
text = 'A message for the child component';
constructor(private cd: ChangeDetectorRef) {
}
ngAfterViewInit() {
this.cd.detectChanges();
}

Related

Sending calculated data from one component to another without Services

I want to send the value from one component to another, they are not related so all solutions are saying that I must use shared service to do that. But these services are using templates (if I'm right). Is there a way to do this sharing without services?
I want to send the BMI value from homepage.component.ts to result.component.ts.
homepage.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
constructor() { }
myHeight!:number;
myWeight!:number;
bmi!:number;
ngOnInit(): void {
}
onGenerate( height:string,width:string){
this.myHeight = +height;
this.myHeight=Math.pow(this.myHeight/100,2);
this.myWeight = +width;
this.bmi=this.myWeight/this.myHeight
console.log(this.bmi); //this is the calculated value to send
}
}
result.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css']
})
export class ResultComponent implements OnInit {
constructor() { }
//I want to get the bmi here
ngOnInit(): void {
}
}
There are Two ways to communicate between unrelated components in angular:
1 - Through services, you have to understand where to inject it, in your case I think it should be injected in root, so try this with your service ( follow this tutorial to implement your service, just add my code instead of theirs )
#Injectable({
providedIn: 'root',
})
2 - Through a store ( a lot of boilerplate coding, to use if you have complexe states to keep synchronized through the whole app, by the way the store is basically a service )
If your components are not related then you can create a shared service between them. Then, you need to use dependency injection to communicate between these components. So, there is a great Angular tutorial which describes how to do it.
The service code would look like this:
#Injectable()
export class FooService {
constructor( ) { }
private yourData;
setData(data){
this.yourData = data;
}
getData(){
let temp = this.yourData;
this.clearData();
return temp;
}
}
and sender component:
import { Router } from '#angular/router';
import { FooService} from './services/foo.service';
export class SenderComponent implements OnInit {
constructor(
private fooService: FooService,
private router:Router) {}
somefunction(data){
this.fooService.setData(data);
this.router.navigateByUrl('/reciever');//as per router
}
}
and subscriber:
import { Router } from '#angular/router';
import { TransfereService } from './services/transfer.service';
export class RecieverComponent implements OnInit {
data;
constructor(
private fooService: FooService){
}
ngOnInit() {
data = this.transfereService.getData();
console.log(`data: `, data)
}
}
Solution: To pass the data from one component to another we can store it in a session storage or a local storage and then access it in other components from that storage. Here I have provided a sample code using local storage for your reference.
homepage.component.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-homepage',
templateUrl: './homepage.component.html',
styleUrls: ['./homepage.component.css']
})
export class HomepageComponent implements OnInit {
constructor() { }
myHeight!:number;
myWeight!:number;
data:string='';
bmi!:number;
ngOnInit(): void {
}
onGenerate( height:string,width:string){
this.myHeight = +height;
this.myHeight=Math.pow(this.myHeight/100,2);
this.myWeight = +width;
this.bmi=this.myWeight/this.myHeight;
this.data=localStorage.setItem('bmi',this.bmi);
console.log(this.bmi); //this is the calculated value to send
}
}
resultcomponent.ts:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css']
})
export class ResultComponent implements OnInit {
data:any;
constructor() { this.data=localstorage.getItem('bmi')}
//Access the bmi using the data variable here
ngOnInit(): void {
}
}

HTML button onclick performs action on other HTML page

It is a simple problem but I cannot get around it as I am new to angular and web development. Basically there are two components home and dashboard. The button in home.component.html changes the source of the image from bulbOn.png to bulbOff.png. I want that the same button should also change the source similarly also on dashboard.component.html. I think I need to use typescript for that but I dont know how. Basically how should onClick on one html performs actions on other html?
home.component.html
<mat-card >
<button onclick="document.getElementById('myImage').src='assets/BulbOn.svg'">Turn on the bulb.</button>
<img id="myImage" src="assets/BulbOn.svg" style="width:100px">
<button onclick="document.getElementById('myImage').src='assets/BulbOff.svg'">Turn off the bulb.</button>
</mat-card>
dashboard.component.html
<mat-card class="bulbCard">
<div class="bulbimg"> <img src="assets/BulbOn.svg"> </div>
</mat-card>
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.less']
})
export class DashboardComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
home.component.ts
import { Component } from '#angular/core';
import { User } from '#app/_models';
import { AccountService } from '#app/_services';
#Component({ templateUrl: 'home.component.html',
styleUrls: ['./home.component.less'] })
export class HomeComponent {
user: User;
constructor(private accountService: AccountService) {
this.user = this.accountService.userValue;
}
}
You should avoid manipulating the DOM like this with Angular.
And instead of using onclick, you should use Angulars event bindings. https://angular.io/guide/event-binding
<mat-card>
<button (click)="changeBulbState(true)">
Turn on the bulb.
</button>
<img [src]="bulbState ? 'assets/BulbOn.svg' : 'assets/BulbOff.svg'" style="width:100px">
<button (click)="changeBulbState(false)">
Turn off the bulb.
</button>
</mat-card>
Within your component's typescript add a variable for bulbState. The value of which will be changed when you interact with the buttons in your card.
The src of the image will change depending on if the bulbState variable is true or false.
import { Component } from '#angular/core';
import { User } from '#app/_models';
import { AccountService } from '#app/_services';
#Component({ templateUrl: 'home.component.html',
styleUrls: ['./home.component.less'] })
export class HomeComponent {
user: User;
bulbState: boolean;
constructor(
private accountService: AccountService,
private bulbStatusService: BulbStatusService
) {
this.user = this.accountService.userValue,
this.bulbStatusService.bulbStatus.subscribe(data => this.bulbState = value)
}
changeBulbState(state: boolean) {
this.bulbStatusService.changeBulbState(state);
}
}
In order to share this across multiple components I would suggest using a service.
https://medium.com/front-end-weekly/sharing-data-between-angular-components-f76fa680bf76
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class BulbStatusService {
private bulbState = new BehaviorSubject(false);
bulbStatus = this.bulbState.asObservable();
constructor() { }
changeBulbState(state: boolean) {
this.bulbState.next(state)
}
}
What you would like to is to have a bulb state somewhere. Usually in Angular it's either the parent component which passes the state down to it's children or you can have a service to get/set the state. RxJS comes bundled with Angular and has some great utilities (observables) for sharing the state.
e.g. app-state.service.ts
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class AppState {
public readonly lightBulb = new BehaviorSubject<'on' | 'off'>('on');
}
Now inject this to you home component:
import { Component } from '#angular/core';
import { User } from '#app/_models';
import { AccountService } from '#app/_services';
import { AppState } from 'app-state.service';
#Component({ templateUrl: 'home.component.html',
styleUrls: ['./home.component.less'] })
export class HomeComponent {
user: User;
constructor(
private accountService: AccountService,
public state: AppState
) {
this.user = this.accountService.userValue;
}
}
In HTML:
<mat-card>
<button (click)="state.lightBulb.next('on')">Turn on the bulb.</button>
<img id="myImage" [src]="(state.lightBulb | async) === 'on' ? 'assets/BulbOn.svg' : 'assets/BulbOff.svg'" style="width:100px">
<button (click)="state.lightBulb.next('off')">Turn off the bulb.</button>
</mat-card>
Then do the same thing for dashboard component:
import { Component } from '#angular/core';
import { AppState } from 'app-state.service';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.less']
})
export class DashboardComponent {
constructor(public state: AppState) { }
}
And in HTML:
<mat-card class="bulbCard">
<div class="bulbimg"><img [src]="(state.lightBulb | async) === 'on' ? 'assets/BulbOn.svg' : 'assets/BulbOff.svg'"></div>
</mat-card>
So in the nut shell, Subjects are things that holds some value and that value can be changed with Subject.next([value here]).
Subjects are Observables and Observables can be subscribed to to get those values over time. In Angular we have async pipe which does this subscription for you and disposes that as well after the component is destroyed.
There are few things you could do better with this "observable store pattern" but here it is at it's simplest form.
Few notes related to other things: use (click) instead onclick as () is the Angular's way to bind outputs. Don't directly manipulate (or at least avoid) anything in DOM e.g. ´document.getElementById('myImage').src='assets/BulbOn.svg'´ but rather bind a value for that attribute with [] e.g. [bulbSvgSource] where ´bulbSvgSource´ would be defined in the component class.

How to use the input of one component in another - Angular4

I'm a new in Angular and I have a problem: I need to use one variable from ComponentA in ComponentB So this is my code below (I need to use "favoriteSeason" input result in component "Result"
Component A
import { Component } from '#angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl, ValidatorFn }
from '#angular/forms';
import {MatRadioModule} from '#angular/material/radio';
import { ResultComponent } from '../result/result.component';
import { HostBinding } from '#angular/core';
#Component({
selector: 'app-answer-three',
templateUrl: './answer-three.component.html',
styleUrls: ['./answer-three.component.css']
})
export class AnswerThreeComponent {
disableBtn: boolean;
favoriteSeason: string;
seasons: string[] = ['Cheesburger', 'Cheesecake', 'Fondue', 'Pizza'];
submit() {
this.disableBtn = !this.disableBtn;
const result = this.favoriteSeason;
console.log(result);
}
}
<div class="co">
<mat-radio-group class="example-radio-group" [(ngModel)]="favoriteSeason" (ngSubmit)="submit()">
<div class="form-check">
<h1>Choose a food:</h1>
</div>
<mat-radio-button class="example-radio-button" *ngFor="let season of seasons" [value]="season">
{{season}}
</mat-radio-button>
</mat-radio-group>
<div class="example-selected-value">Your favorite food is: {{favoriteSeason}}</div>
<nav>
<div class="column">
<button class="btn btn-primary" [disabled]="disableBtn" name="button" (click)="submit()">save
</button>
<button class="btn btn-primary" [disabled]="!disableBtn" name="button" (click)="submit()">
<a routerLink="/result">Next</a>
</button>
</div>
</nav>
</div>
And I need to use the result of "favoriteSeason" in component Result
Component B
import { NgModule, Output } from '#angular/core';
import { Component, OnInit, Input } from '#angular/core';
import {Subject} from 'rxjs';
import { Injectable } from '#angular/core';
import { AnswerThreeComponent } from '../answer-three/answer-three.component';
import { HostListener } from '#angular/core';
#Component({
selector: 'app-result',
templateUrl: './result.component.html',
styleUrls: ['./result.component.css'],
})
export class ResultComponent {
#Input () answer: AnswerThreeComponent;
#Input () res: AnswerThreeComponent['submit'];
#HostListener('click')
click() {
const result = this.answer.favoriteSeason;
console.log(this.answer.favoriteSeason);
}
}
But i received an error - "can't find favoriteSeason name". What I do wrong? Thank you for any help and sorry if I wrote this question wrong (it's my first time)
Sanchit Patiyal's answer is correct, but I would like to elaborate on that.
When you use the #Input() decorator on a component's field, that means that you now can set that variable in a parent component's template. Consider the following example:
Component A:
#Component({
selector: 'aComponent',
templateUrl: './a.component.html'
})
export class AComponent {
inputValue: string;
//doesn't matter what you do here
}
<input [(ngModel)]="inputValue">
<bComponent [inputValue]="inputValue">
</bComponent>
Component B:
#Component({
selector: 'bComponent',
templateUrl: './b.component.html'
})
export class BComponent {
#Input() inputValue: string;
//this variable will be set by the parent component
}
<h1>{{inputValue}}</h1>
This is an example of one-way data flow, meaning that the data only flows into the component B. Now if you need to get some data out of the component, you need to use #Output() decorator, which uses an EventEmitter to emit events out to the parent component when something happens. Let's introduce the third component, cComponent:
#Component({
selector: 'cComponent',
templateUrl: './c.component.html'
})
export class CComponent {
#Output() output: EventEmitter<string>;
private counter: number;
click() {
this.output.next(counter++);
}
}
<button (click)="click()">Click me!</button>
...and then edit our AComponent like this:
#Component({
selector: 'aComponent',
templateUrl: './a.component.html'
})
export class AComponent {
inputValue: string;
buttonClicked(event: string) {
this.inputValue = event;
}
}
<cComponent (output)="buttonClicked($event)"></cComponent>
<bComponent [inputValue]="inputValue"></bComponent>
So, to recap, the component's output works just like other events (say (click) or (focus)), and can be used to get the data out of the component. HOpe this helps ;)
Welcome, by the way!
You can use RxJS BehaviorSubject for this. In this case we create a private BehaviorSubject that will hold the current value of the message which can be set in one component and get in other one. Follow this link for the tutorial.
Sharing Data Between Angular Components

Confusing behavior of a BehaviorSubject in my Angular App

I recently ran into a problem and can't really figure out what's wrong with my code at this point, hopefully someone of you can help me.
All I am trying to do is changing the value of my BehaviorSubject with a function but it isn't working out.
chat.service.ts
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ChatService {
chatId = new BehaviorSubject<number>(0);
constructor() {
this.chatId.next(1);
}
changeChatId(chatId: number) {
console.log(chatId);
this.chatId.next(chatId);
}
}
So the subscribers get the default as well as the changed chatId from the constructor. But as soon as I try to change it with the changeChatId function nothing happens at all. The right id's get passed into the function I already debugged that but the line this.chatId.next(chatId) doesn't seem to do anything.
ADD
These are the other components the service is currently used in.
chat-message-list
import { Component, OnInit, Input} from '#angular/core';
import { ChatService } from "../../../shared/services/chat.service";
#Component({
selector: 'app-chat-message-list',
templateUrl: './chat-message-list.component.html',
styleUrls: ['./chat-message-list.component.css'],
providers: [ChatService]
})
export class ChatMessageListComponent implements OnInit {
chatId: number;
constructor(private chat: ChatService) { }
ngOnInit() {
this.chat.chatId.subscribe(
chatId => this.updateMessageList(chatId)
);
}
}
chat-item
import { Component, OnInit, Input} from '#angular/core';
import { User } from '../../../shared/models/user.model';
import { ChatService } from '../../../shared/services/chat.service';
#Component({
selector: 'app-chat-list-item',
templateUrl: './chat-list-item.component.html',
styleUrls: ['./chat-list-item.component.css'],
providers: [ChatService]
})
export class ChatListItemComponent implements OnInit {
#Input()
user: User;
constructor(private chat: ChatService) { }
ngOnInit() {
}
onChatItemSelected(){
this.chat.changeChatId(this.user.id);
}
}
You need to make your ChatService a singleton (shared) service. Add it to the providers of your ngModule. This allows all the components that use the ChatService to share the same service instance.
#NgModule({
providers: [ChatService]
})
And remove it from your components providers. When you are adding it to your components providers, that component gets its own instance of ChatService which can not be used by other components.

angular 2 output and eventemitter doesn't emit

i'm learning angular 2, and i follow this tutorial: https://egghead.io/lessons/angular-2-angular-2-building-a-toggle-button-component
but the whole part of the output and eventemitter doesn't work.
i do not get any errors and i can't figure out why it doesn't work.
this is my code for the togglelink component:
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
moduleId: module.id,
selector: 'app-togglelink',
templateUrl: 'togglelink.component.html',
styleUrls: ['togglelink.component.css']
})
export class TogglelinkComponent implements OnInit {
#Input() state:boolean = true;
#Output() onChange = new EventEmitter();
onClick(){
this.state = !this.state;
this.onChange.emit(this.state);
}
constructor() {}
ngOnInit() {
}
}
and this is the code for the firstpage component that uses the togglelink component:
import { Component, OnInit } from '#angular/core';
import {TogglelinkComponent} from '../togglelink/togglelink.component';
#Component({
moduleId: module.id,
selector: 'app-firstpage',
templateUrl: 'firstpage.component.html',
styleUrls: ['firstpage.component.css'],
directives: [TogglelinkComponent]
})
export class FirstpageComponent implements OnInit {
thetogglestate:boolean = false;
firsttitle = "the first title";
constructor() {}
ngOnInit() {
}
}
and this is the code for the template of the firstpage component:
{{thetogglestate ? 'On' : 'Off'}}
</p>
<h2 *ngIf="thetogglestate">
{{firsttitle}}
</h2>
<p>
firstpage works!
</p>
when i change manually thetogglestate it does work, so i understand that the issue is with the output and the eventemitter part.
any idea why?
best regards
In the firstpage.component.html template, you need to register some processing for this event to toggle the value of the thetogglestate variable.
Something like that:
<app-togglelink (onChange)="thetogglestate = !thetogglestate"></app-togglelink>

Categories