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");
});
}
Related
Currently, what I have is this.
export class TableComponent<T> implements OnInit {
displayedColumns: Array<string> = [];
dataSource: MatTableDataSource<T> = new MatTableDataSource();
constructor(public asmErrorServ: AssemblyErrorService) { }
ngOnInit(): void {
this.displayedColumns = ['Message Type', 'Message', 'Message Date'];
this.dataSource = (<any>this.asmErrorServ).asmErrorLog;
}
}
The this.datasource gets updated from the asmErrorServ which has an array that stores errors or warnings real-time. That part of the code appears like this in the errorservice.
export class AssemblyErrorService{
...
error(message: string): void {
let newError: asmMessage = { type: 'error', message: message, time: Date.now() };
this.asmErrorLog.push(newError);
this._snackBar.open(this.generateSnackbarString(newError), 'Dismiss', {
duration: 3000,
horizontalPosition: this.horizontalPosition,
verticalPosition: this.verticalPosition,
panelClass: ['red-snackbar', 'login-snackbar'],
});
}
}
The out looks like this and the table does not seem to get updated real-time. I was wondering on how to fix it if there's a way to do it?
You have to re-initialize the data source property, like below:
this.asmErrorLog.push(newError);
this.dataSource.data = this.asmErrorLog;
Now the table will be updated in real time.
I am using the below code to navigate to parent component on click of "Device Hardware Back Button". I am using Capacitor 3.0 and device backbutton works properly.
Actual issue is that i am not able to access Class members in the callback function.
Below is the code
export class ConfirmBoxComponent extends DialogContentBase implements OnInit{
constructor(public dialog: DialogRef, public myService : MyService) {
super(dialog);
}
ngOnInit(): void {
this.handleHardwareBackButton();
}
public onCancelAction(): void {
console.log("Cancel confirmed", this.dialog)
myService.closeDialog();// closeDialog not available thru arrow or callback functions
}
handleHardwareBackButton(){
App.addListener('backButton',function(){
this.onCancelAction() //not able to access onCancelMethod within callback
})
}
}
Issue is that i am getting "this.onCancelAction" is not a method. Also i tried below code but no use.
handleHardwareBackButton(){
App.addListener('backButton',function(){
this.dialog.close({ actionConfirmed : false }); //here this line doesn't get executed. Also no errors observed
}.bind(this))
}
Am i going wrong somewhere? Please guide me on how to access class members in a callback function?
Try something like this...
export class ConfirmBoxComponent extends DialogContentBase implements OnInit {
constructor(public dialog: DialogRef) {
super(dialog);
}
ngOnInit(): void {
this.handleHardwareBackButton();
}
public onCancelAction(): void {
console.log("Cancel confirmed", this.dialog)
this.dialog.close({ actionConfirmed: false });
}
handleHardwareBackButton() {
App.addListener('backButton', () => {
this.onCancelAction() //not able to access onCancelMethod within callback
})
}
}
I need to run a method with 2 parameters, each parameter is gotten through some form of subscribe function. the first is the collection which is gotten through the url from angular's page routing. The second is the dokument, this is the firebase's firestore document.
export class FirebaseDocument implements OnInit {
collection: string;
dokument: any;
//== CONSTRUCTORS
constructor(
private route: ActivatedRoute,
private _db: AngularFirestore
) {}
//== Initialize
ngOnInit() {
console.log("__loading page component");
this.route.params.subscribe(params => {
this.collection = params["collection"];
});
console.log(this.collection);//collection populated correctly
//load the document from AngularFirestore
console.log("loading the document from firebase");
let itemsCollection = this._db.collection(url).valueChanges();
//subscribe to get the dok of the first document in the collection
itemsCollection.subscribe(docArr => {
this.dokument = docArr[0];
console.log(this.dokument);//dokument is populated
});
console.log(this.dokument);//dokument is undefined
this.doMultiParameterMethod(this.collection, this.dokument);
}
}
this.collection populates perfectly fine;
this.dokument is only populated inside the subscribe method
I need this to be populated by the time the next line is run. the console.log(this.dokument);
I have been dumbstruck by this because essentially the same code is used by the 2 subscribe methods but they don't behave the same way.
Sometimes a subscribe can be synchronous. This happens when the Observable is a ReplaySubject a BehaviorSubject or an Observable which has a shareReplay() pipe. (probably other options as well.
This will make the observable immediately fire on subscription. However, you should never count on this behavior, and always continue within your subscribe.. Or use pipes like mergeMap and create other observables which you can access in your template using the async pipe.
In your case. The this.route.params is obviously a 'replaying' Observable from which you get the latest value after subscribing. Otherwise you would have to wait for the params to change again until you get a value.
Your Database call cannot return an immediate response, because it's essentially a network request.
In your example code, you can update it to this, and use the async pipe in your template
export class FirebaseDocument implements OnInit {
readonly collection$: Observable<string> = this.route.params.pipe(
map((params) => params.collection)
);
readonly doc$: Observable<any[]> = this.db.collection(this.url).valueChanges().pipe(
shareReplay({ refCount: true, bufferSize: 1 })
);
constructor(private route: ActivatedRoute, private db: AngularFirestore) {}
ngOnInit() {
// don't forget to unsubscribe
combineLatest([
this.collection$,
this.doc$
]).subscribe((collection, document) => {
this.doMultiParameterMethod(collection, document);
});
}
}
Maybe you should make the Observable a Promise, in your case would be the following :
export class FirebaseDocument implements OnInit {
collection: string;
dokument: any;
//== CONSTRUCTORS
constructor(
private route: ActivatedRoute,
private _db: AngularFirestore
) {}
//== Initialize
ngOnInit() {
console.log("__loading page component");
this.route.params.subscribe(params => {
this.collection = params["collection"];
});
console.log(this.collection); //collection populated correctly
this.getDokument().then(docArr => {
this.dokument = docArr[0];
this.doMultiParameterMethod(this.collection, this.dokument);
});
}
getDokument(): Promise<any> {
let itemsCollection = this._db.collection(url).valueChanges();
return new Promise((resolve, reject) => {
itemsCollection.subscribe((response: any) => {
resolve(response);
}, reject);
});
}
}
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);
}
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>