Angular 7: ExpressionChangedAfterItHasBeenCheckedError while using Rxjs observbles - javascript

I am trying to share data between components using the rxjs subject and i've used that data in component
Component.html
<div class="spinner-container" *ngIf="loading">
<div class="spinner-item">
<nx-spinner nxSize="large"></nx-spinner>
</div>
</div>
component.ts
ngOnInit(){
setTimeout(()=>{
this.commonService.spinnerTrigger.subscribe((trigger)=>{
this.loading = trigger;
})
},100)
}
Here is the error
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed
after it was checked. Previous value: 'ngIf: false'. Current value:
'ngIf: true'.
I found a workaround using changedetectref but I don't think its good practice is ther any other way to solve this issue

You can manually trigger change detection using the detectChanges() method of the ChangeDetectorRef
Try like this:
import { ChangeDetectorRef} from '#angular/core';
constructor(private cdr: ChangeDetectorRef) { }
ngOnInit(){
setTimeout(()=>{
this.commonService.spinnerTrigger.subscribe((trigger)=>{
this.loading = trigger;
if (this.cdr && !(this.cdr as ViewRef).destroyed) {
this.cdr.detectChanges();
}
})
},100)
}

Making the next callback async worked for me once:
this.commonService.spinnerTrigger.subscribe(async (trigger) => {
this.loading = await trigger;
});
Or adding a zero delay:
this.commonService.spinnerTrigger.pipe(delay(0)).subscribe((trigger) => {
this.loading = trigger;
});

This is an open issue in Github,
Github issue => https://github.com/angular/angular/issues/15634
And they provided a workaround using setTimeout() for now and still there aren't any updates regarding this issue.
And also you can try changeDetector that may solve your issue.
import { ChangeDetectorRef } from '#angular/core';
constructor(private cdRef:ChangeDetectorRef) {}
ngAfterViewChecked()
{
this.cdRef.detectChanges();
}

I don't see any need here to mess around with change detection / setTimeout (which triggers change detection).
Stackblitz
Use a spinner service which parent and child can use.
spinner.service.ts
#Injectable()
export class SpinnerService {
private loading = new BehaviorSubject<boolean>(true)
loading$: Observable<boolean> = this.loading.asObservable()
setSpinner(bool: boolean) {
this.loading.next(bool)
}
}
Example - Component setting spinner
ngOnInit() {
this.service.getChildData().pipe(
// handle any errors
catchError(err => {
console.log('Error caught: ', err)
this.data = err
return throwError(err)
}),
// no matter what set spinner false
finalize(() => {
this.spinnerService.setSpinner(false)
}),
// subscription clean up
takeUntil(this.destroyed$)
).subscribe(data => this.data = data)
}
Example - parent / container displaying spinner
ngOnInit() {
this.loading$ = this.spinnerService.loading$
this.spinnerService.setSpinner(true) // if needed
}
<div *ngIf="loading$ | async">
I am a spinner
</div>

Related

Values are undefined on load time

I'm working on angular app.
In my service file, I created a function configure. And it as been called in AfterViewInit in an component.
But On load time, this.config is undefined, If I use it inside setTimeOut I could able to access the value of the this.config.
The below code works,
configure() {
setTimeout(() => {
if(this.config) {
this.apply();
}
}, 200);
}
Is there any better way to do it ? without using setTimeOut.
Please help
try with markForCheck method
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit(){
{
this.cdr.markForCheck();
this.configure()
}
configure(){
if(this.config) {
// your code
}
}
add the below code to #Component decorator
changeDetection: ChangeDetectionStrategy.OnPush,
Can you please try with below code
configure() {
var self = this;
setTimeout(() => {
if(self.config) {
self.apply();
}
}, 200);
}

How to fetch data again from an API after something changed in a component using Observables?

I have a DataServive, that fetches content from an API:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError, retry } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
#Injectable()
export class DataService {
this.request = {
count: 10
}
constructor(private http: HttpClient) { }
private handleError(error) {
console.log(error);
}
public getData(count): Observable<any> {
this.request.count = count;
return this.http.post<any>(environment.api + '/path', this.request).pipe(
map(response => {
return response;
}),
catchError(error => {
this.handleError(error);
return [];
})
);
}
}
This DataServie is consumned by a component like this:
ngOnInit() {
const subscriber = this.dataService.getData(this.count).subscribe((data) => { this.data = data; });
}
And it works fine.
However the user is able to change the variable this.count (how many items should be displayed) in the component. So I want to get new data from the server as soon as this value changes.
How can I achieve this?
Of course I could call destroy on this.subscriber and call ngOnInit() again, but that dosn't seem like the right way.
Easiest ways is just to unsubscribe:
subscriber: Subscription;
ngOnInit() {
this.makeSubscription(this.count);
}
makeSubscription(count) {
this.subscriber = this.dataService.getData(this.count).subscribe((data) => { this.data = data; });
}
functionInvokedWhenCountChanges(newCount) {
this.subscriber.unsubscribe();
makeSubscription(newCount);
}
But because count argument is just a one number it means HTTP always asks for data from 0 to x. In this case, you can better create another subject where you can store previous results (so you don't need to make useless HTTP requests) and use that subject as your data source. That needs some planning on your streams, but is definitely the preferred way.
When the user changes count, call getData(count) again with the updated count value. Need to see your html file, but having a button with (click)="getData(count)" may help.

How to listen for value changes from class property TypeScript - Angular

In AngularJS, we can listen variable change using $watch, $digest... which is no longer possible with the new versions of Angular (5, 6).
In Angular, this behaviour is now part of the component lifecycle.
I checked on the official documention, articles and especially on Angular 5 change detection on mutable objects, to find out how to listen to a variable (class property) change in a TypeScript class / Angular
What is proposed today is :
import { OnChanges, SimpleChanges, DoCheck } from '#angular/core';
#Component({
selector: 'my-comp',
templateUrl: 'my-comp.html',
styleUrls: ['my-comp.css'],
inputs:['input1', 'input2']
})
export class MyClass implements OnChanges, DoCheck, OnInit{
//I can track changes for this properties
#Input() input1:string;
#Input() input2:string;
//Properties what I want to track !
myProperty_1: boolean
myProperty_2: ['A', 'B', 'C'];
myProperty_3: MysObject;
constructor() { }
ngOnInit() { }
//Solution 1 - fired when Angular detects changes to the #Input properties
ngOnChanges(changes: SimpleChanges) {
//Action for change
}
//Solution 2 - Where Angular fails to detect the changes to the input property
//the DoCheck allows us to implement our custom change detection
ngDoCheck() {
//Action for change
}
}
This is only true for #Input() property !
If I want to track changes of my component's own properties (myProperty_1, myProperty_2 or myProperty_3), this will not work.
Can someone help me to solve this problematic ? Preferably a solution that is compatible with Angular 5
You can still check component's field members value change by KeyValueDiffers via DoCheck lifehook.
import { DoCheck, KeyValueDiffers, KeyValueDiffer } from '#angular/core';
differ: KeyValueDiffer<string, any>;
constructor(private differs: KeyValueDiffers) {
this.differ = this.differs.find({}).create();
}
ngDoCheck() {
const change = this.differ.diff(this);
if (change) {
change.forEachChangedItem(item => {
console.log('item changed', item);
});
}
}
see demo.
I think the nicest solution to your issue is to use a decorator that replaces the original field with a property automatically, then on the setter you can create a SimpleChanges object similar to the one created by angular in order to use the same notification callback as for angular (alternatively you could create a different interface for these notifications, but the same principle applies)
import { OnChanges, SimpleChanges, DoCheck, SimpleChange } from '#angular/core';
function Watch() : PropertyDecorator & MethodDecorator{
function isOnChanges(val: OnChanges): val is OnChanges{
return !!(val as OnChanges).ngOnChanges
}
return (target : any, key: string | symbol, propDesc?: PropertyDescriptor) => {
let privateKey = "_" + key.toString();
let isNotFirstChangePrivateKey = "_" + key.toString() + 'IsNotFirstChange';
propDesc = propDesc || {
configurable: true,
enumerable: true,
};
propDesc.get = propDesc.get || (function (this: any) { return this[privateKey] });
const originalSetter = propDesc.set || (function (this: any, val: any) { this[privateKey] = val });
propDesc.set = function (this: any, val: any) {
let oldValue = this[key];
if(val != oldValue) {
originalSetter.call(this, val);
let isNotFirstChange = this[isNotFirstChangePrivateKey];
this[isNotFirstChangePrivateKey] = true;
if(isOnChanges(this)) {
var changes: SimpleChanges = {
[key]: new SimpleChange(oldValue, val, !isNotFirstChange)
}
this.ngOnChanges(changes);
}
}
}
return propDesc;
}
}
// Usage
export class MyClass implements OnChanges {
//Properties what I want to track !
#Watch()
myProperty_1: boolean = true
#Watch()
myProperty_2 = ['A', 'B', 'C'];
#Watch()
myProperty_3 = {};
constructor() { }
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
}
}
var myInatsnce = new MyClass(); // outputs original field setting with firstChange == true
myInatsnce.myProperty_2 = ["F"]; // will be notified on subsequent changes with firstChange == false
as said you can use
public set myProperty_2(value: type): void {
if(value) {
//doMyCheck
}
this._myProperty_2 = value;
}
and then if you need to retrieve it
public get myProperty_2(): type {
return this._myProperty_2;
}
in that way you can do all the checks that you want while setting/ getting your variables such this methods will fire every time you set/get the myProperty_2 property.
small demo: https://stackblitz.com/edit/angular-n72qlu
I think I came into the way to listen to DOM changes that you can get any changes that do to your element, I really hope these hints and tips will help you to fix your problem, following the following simple step:
First, you need to reference your element like this:
in HTML:
<section id="homepage-elements" #someElement>
....
</section>
And in your TS file of that component:
#ViewChild('someElement')
public someElement: ElementRef;
Second, you need to create an observer to listen to that element changes, you need to make your component ts file to implements AfterViewInit, OnDestroy, then implement that ngAfterViewInit() there (OnDestroy has a job later):
private changes: MutationObserver;
ngAfterViewInit(): void {
console.debug(this.someElement.nativeElement);
// This is just to demo
setInterval(() => {
// Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
this.renderer.setAttribute(this.someElement.nativeElement, 'my_custom', 'secondNow_' + (new Date().getSeconds()));
}, 5000);
// Here is the Mutation Observer for that element works
this.changes = new MutationObserver((mutations: MutationRecord[]) => {
mutations.forEach((mutation: MutationRecord) => {
console.debug('Mutation record fired', mutation);
console.debug(`Attribute '${mutation.attributeName}' changed to value `, mutation.target.attributes[mutation.attributeName].value);
});
}
);
// Here we start observer to work with that element
this.changes.observe(this.someElement.nativeElement, {
attributes: true,
childList: true,
characterData: true
});
}
You will see the console will work with any changes on that element:
This is another example here that you will see 2 mutation records fired and for the class that changed:
// This is just to demo
setTimeout(() => {
// Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds()));
this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds() + 1));
}, 5000);
// Here is the Mutation Observer for that element works
this.changes = new MutationObserver((mutations: MutationRecord[]) => {
mutations.forEach((mutation: MutationRecord) => {
console.debug('Mutation record fired', mutation);
if (mutation.attributeName == 'class') {
console.debug(`Class changed, current class list`, mutation.target.classList);
}
});
}
);
Console log:
And just housekeeping stuff, OnDestroy:
ngOnDestroy(): void {
this.changes.disconnect();
}
Finally, you can look into this Reference: Listening to DOM Changes Using MutationObserver in Angular
You can import ChangeDetectorRef
constructor(private cd: ChangeDetectorRef) {
// detect changes on the current component
// this.cd is an injected ChangeDetector instance
this.cd.detectChanges();
// or run change detection for the all app
// this.appRef is an ApplicationRef instance
this.appRef.tick();
}

Angular 4 Error: Expression has changed after it was checked

I am using Angular Material in my Angular 4 app. When I try to use the MatSnackBar in the ngAfterViewInit(), I am facing an error as:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed
after it was checked. Previous value: 'undefined'. Current value:
'visible-bottom'.It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?
I have used the ChangeDetectorRef to detect the changes, but it doesn't seem to work.
Here's the code I have been working on:
constructor(private matSnackBar: MatSnackBar, private router: Router, private cdr: ChangeDetectorRef) { }
ngOnInit() {}
ngAfterViewInit() {
let snackBarRef = this.matSnackBar.open('Redirecting to dashboard..', 'Cancel', {
duration: 10000
});
snackBarRef.onAction().subscribe(() => {
console.log("Cancelled");
});
this.cdr.detectChanges();
}
Please help me resolve this issue.
One potential but crude solution is to use setTimeout -
let snackBarRef;
setTimeout(() => {
snackBarRef = this.matSnackBar.open('Redirecting to dashboard..', 'Cancel', {
duration: 10000
});
});
please refer https://github.com/angular/material2/issues/6158 for more information.
Try to move snackbar in your constructor:
constructor(private matSnackBar: MatSnackBar, private router: Router, private cdr: ChangeDetectorRef) {
let snackBarRef = this.matSnackBar.open('Redirecting to dashboard..', 'Cancel', {
duration: 10000
});
snackBarRef.onAction().subscribe(() => {
console.log("Cancelled");
});
}

Trying to understand the Promise in angular 2

I have created a service for google API and stacked at the promise response. Let me show you my code:
getPromise: Promise<any>;
loadSheets: Array<any>;
constructor(public _checkAuthApiService: CheckAuthApiService) { }
ngOnInit() {
if (this._checkAuthApiService) {
this._checkAuthApiService.checkAuth().then(res => {
if (res && !res.error) {
this.loadSheetsFunc();
}
});
}
//setTimeout(function(){},1000); //When i add this it works :(
}
loadSheetsFunc = () => {
this.getPromise = new Promise((resolve: any, reject: any) => {
resolve(this._checkAuthApiService.loadSheetsApi())
});
this.getPromise.then((res: any) => this.sheetsResults(res));
};
sheetsResults = (res: any) => this.loadSheets = res.result.values;
not sure what i am missing but when i add seTimeout within ngOnInit ti works i get the data i want on the view. Can someone help me with this code or perhaps suggest me a better way using Observables. Thank you in advance.
The setTimeout() causes a full Angular2 change detection cycle, which is why this makes Angular2 recognize the updated property.
That Angular2 doesn't recognize the update without setTimeout() indicates an issue with polyfills (perhaps loading order).
I would make a few changes to your code
loadSheets: Array<any>;
constructor(public _checkAuthApiService: CheckAuthApiService) { }
ngOnInit() {
if (this._checkAuthApiService) {
this._checkAuthApiService.checkAuth().then(res => {
if (res && !res.error) {
this.loadSheetsFunc();
}
});
}
}
loadSheetsFunc() {
this._checkAuthApiService.loadSheetsApi()
subscribe(val => this.sheetsResults(res));
}
sheetsResults(res: any) {
this.loadSheets = res.result.values;
}
I don't know though what loadSheetsApi() returns (assuming Observable).

Categories