Angular prerendering times out with setInterval() - javascript

I have a Visual Studio 2017 .NET Core 2.0 project with Angular template. Angular prerendering times out when I uncomment either of the "this.SetUpRefreshInterval()" lines below. The app returns error "NodeInvocationException: Prerendering timed out after 30000ms because the boot function in 'ClientApp/dist/main-server' returned a promise that did not resolve or reject. Make sure that your boot function always resolves or rejects its promise." Any ideas?
Here's the component code:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-currentdatetime',
templateUrl: './currentdatetime.component.html',
styleUrls: ['./currentdatetime.component.css']
})
export class CurrentDateTimeComponent implements OnInit {
private currentDateTime: Date;
constructor() {
this.refreshDate();
//this.setUpRefreshInterval();
}
get currentDateFormatted(): String {
return this.currentDateTime.toLocaleDateString();
};
get currentTimeFormatted(): String {
return this.currentDateTime.toLocaleTimeString();
};
ngOnInit(): void {
//this.setUpRefreshInterval();
}
private setUpRefreshInterval(): void {
setInterval(() => {
this.refreshDate();
}, 1000);
}
private refreshDate(): void {
this.currentDateTime = new Date();
}
}
I also tried using Observable.timer and got the same result:
private setUpRefreshInterval(): void {
let timer = Observable.timer(0, 1000);
timer.subscribe(t => this.currentDateTime = new Date());
}
As a workaround, I used Krishnanunni's suggestion and made this change, which works:
import { Component, OnInit, Inject } from '#angular/core';
import { isPlatformBrowser } from '#angular/common';
import { PLATFORM_ID } from '#angular/core';
constructor(#Inject(PLATFORM_ID) private platformId: Object) {
this.refreshDate();
}
ngOnInit(): void {
// setting up the refresh interval caused a timeout in prerendering, so only set up interval if rendering in browser
if (isPlatformBrowser(this.platformId)) {
this.setUpRefreshInterval();
}
}

Wrap the interval code in isPlatformBrowser condition so that it is not pre-rendered

Related

_co.photo is undefined console error and error context, but code works as expected

I got problem with angular component.
When I make my component with selector, it works as expected: execute httpget, and render photo with title.
But in console I got two errors:
ERROR TypeError: "_co.photo is undefined"
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
and
ERROR CONTEXT
...
PhotoHolderComponent.html:2:8
View_PhotoHolderComponent_0 PhotoHolderComponent.html:2
I got html:
<div class="photo-holder">
<h2>{{photo.title}}</h2>
<img src="{{photo.url}}">
</div>
and ts:
import { Component, OnInit } from '#angular/core';
import { Photo } from './photo'
import { PhotoDeliveryService } from '../photo-delivery-service.service'
#Component({
selector: 'app-photo-holder',
templateUrl: './photo-holder.component.html',
styleUrls: ['./photo-holder.component.css']
})
export class PhotoHolderComponent implements OnInit {
photo:Photo
constructor( private photoService : PhotoDeliveryService) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => this.photo = {...data})
}
}
and service :
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Photo } from './photo-holder/photo'
#Injectable({
providedIn: 'root'
})
export class PhotoDeliveryService {
value : Number
url : string
constructor(private http: HttpClient) {
this.url = "https://jsonplaceholder.typicode.com/photos/";
this.value = Math.floor(Math.random() * 10) + 1;
}
getRandomPhoto() {
return this.http.get<Photo>(this.getUrl())
}
getUrl(){
return this.url + this.value;
}
}
I suspect that could be made by binding property before query results was returned.
How can I rid off this problem, can I wait for this query, or this is different kind of problem ?
You are getting the error because before your service could resolve, the template bindings are resolved and at that time photo object is undefined.
first thing, you can initialize the photo object but then you might have to detect the changes using ChangeDetectorRef to reflect the value returned by the service.
photo:Photo = {
title:'',
url:''
};
constructor( private photoService : PhotoserviceService, private cdr:ChangeDetectorRef) {
}
ngOnInit() {
this.photoService.getRandomPhoto().subscribe((data: Photo) => {
this.photo = data;
this.cdr.detectChanges();
});
}

Angular 5 anchor did not scroll

What I am trying to do is to create an anchor link. This link will navigate to a specific scroll point in my page. I have Angular version 5.
Html:
<mat-list>
<mat-list-item><a [routerLink]="['/']"> Intro </a></mat-list-item>
<mat-list-item><a [routerLink]="['/']" fragment="mobile"> Mobile </a></mat-list-item>
...
</mat-list>
In home.componets.ts:
export class HomeGrComponent implements OnInit {
private fragment: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.fragment.subscribe(fragment => { this.fragment = fragment; });
}
ngAfterViewInit(): void {
try {
setTimeout(()=> {
document.querySelector('#' + this.fragment).scrollIntoView();
}, 1000);
} catch (e) { }
}
}
I took this code from this question but it doesn't work. Url is changed to
http://localhost:4200/#mobile
but it didn't scroll to my point.
Also in console there is an error:
Cannot read property 'scrollIntoView' of null
What can be possible goes wrong? If you need some additional information please ask me to reply. Also it could be great the scroll navigates smoothly (optional).
You can use the following Code:
import { Component, OnDestroy } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnDestroy {
private sub: Subscription;
constructor(activeRoute: ActivatedRoute) {
this.sub = activeRoute.fragment.pipe(filter(f => !!f)).subscribe(f => document.getElementById(f).scrollIntoView());
}
public ngOnDestroy(): void {
if(this.sub) this.sub.unsubscribe();
}
}
Working example and Code behind
The reason why it's not working is that ngAfterViewInit is being called before the Observable is resolved, and therefore this.fragment is null, so no element is found
ngOnInit() {
this.route.fragment.subscribe(fragment => {
this.fragment = fragment;
});
}
ngAfterViewInit(): void {
let interval = setInterval(()=> {
let elem = document.getElementById(this.fragment);
if(elem) {
elem.scrollIntoView();
clearInterval(interval);
}
}, 1000);
}
Another option is to use setTimout(). So you don't need clearInterval().
You can also access the fragment with the help of the ActivatedRoute
constructor(private route: ActivatedRoute) {}
ngAfterViewInit(): void {
setTimeout(() => document.querySelector(this.route.snapshot.fragment).scrollIntoView(), 1000);
}

ngFor loop content disapears when leaving page

I am new to Angular and Ionic. I am looping through an array of content that is store in my Firestore database. When the app recompiles and loads, then I go to the settings page (that's where the loop is happening), I see the array of content just fine. I can update it on Firestore and it will update in real time in the app. It's all good here. But if I click "Back" (because Settings is being visited using "navPush"), then click on the Settings page again, the whole loop content will be gone.
Stuff is still in the database just fine. I have to recompile the project to make the content appear again. But once again, as soon as I leave that settings page, and come back, the content will be gone.
Here's my code:
HTML Settings page (main code for the loop):
<ion-list>
<ion-item *ngFor="let setting of settings">
<ion-icon item-start color="light-grey" name="archive"></ion-icon>
<ion-label>{{ setting.name }}</ion-label>
<ion-toggle (ionChange)="onToggle($event, setting)" [checked]="setting.state"></ion-toggle>
</ion-item>
</ion-list>
That Settings page TS file:
import { Settings } from './../../../models/settings';
import { DashboardSettingsService } from './../../../services/settings';
import { Component, OnInit } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
#IonicPage()
#Component({
selector: 'page-dashboard-settings',
templateUrl: 'dashboard-settings.html',
})
export class DashboardSettingsPage implements OnInit {
settings: Settings[];
checkStateToggle: boolean;
checkedSetting: Settings;
constructor(public dashboardSettingsService: DashboardSettingsService) {
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
}
onToggle(event, setting: Settings) {
this.dashboardSettingsService.setBackground(setting);
}
}
And my Settings Service file (the DashboardSettingsService import):
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return this.settings;
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}
Any idea what is causing that?
My loop was in a custom component before, so I tried putting it directly in the Dashboard Settings Page, but it's still not working. I have no idea what to check here. I tried putting the :
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
})
...part in an ngOninit method instead, or even ionViewWillLoad, and others, but it's not working either.
I am using Ionic latest version (3+) and same for Angular (5)
Thank you!
From the Code you posted i have observed two findings that might be the potential cause for the issue ,
Calling of the Service method in the constructor :
When your setting component is created , then that constructor will be called but but if you were relying on properties or data from child components actions to take place like navigating to the Setting page so move your constructor to any of the life cycle hooks.
ngAfterContentInit() {
// Component content has been initialized
}
ngAfterContentChecked() {
// Component content has been Checked
}
ngAfterViewInit() {
// Component views are initialized
}
ngAfterViewChecked() {
// Component views have been checked
}
Even though you add your service calling method in the life cycle events but it will be called only once as you were subscribing your service method in the constructor of the Settings service file . so just try to change your service file as follows :
getSettings() {
this.settingsCollection = this.afs.collection('settings');
this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
Update :
Try to change the Getsettings as follows and please do update your question with the latest changes
getSettings() {
this.settingsCollection = this.afs.collection('settings');
return this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
return data;
});
});
}
I'm not certain, but I suspect the subscription to the settings observable settings: Observable<Settings[]> could be to blame. This may work on the first load because the DashboardSettingsService is being created and injected, therefore loading the settings, and then emitting an item (causing your subscription event in DashboardSettingsPage to fire).
On the second page load, DashboardSettingsService already exists (services are created as singletons by default) - this means that the constructor does not get called (which is where you set up your observable) and therefore it does not emit a new settings object for your component.
Because the Observable does not emit anything, the following event will not be fired, meaning your local settings object is never populated:
this.dashboardSettingsService.getSettings().subscribe(setting => {
this.settings = setting;
console.log(setting.state);
})
You could refactor your service with a method that provides the latest (cached) settings object, or a new Observable (dont forget to unsubscribe!!), rather than creating a single Observable which will only be triggered by creation or changes to the underlying storage object.
Here's a simple example that doesnt change your method signature.
import { Settings } from './../models/settings';
import { Injectable, OnInit } from '#angular/core';
import * as firebase from 'firebase/app';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
#Injectable()
export class DashboardSettingsService implements OnInit {
settings: Observable<Settings[]>;
cachedSettings: Settings[];
settingsCollection: AngularFirestoreCollection<Settings>;
settingDoc: AngularFirestoreDocument<Settings>;
public checkedSetting = false;
setBackground(setting: Settings) {
if (this.checkedSetting == true) {
this.checkedSetting = false;
} else if(this.checkedSetting == false) {
this.checkedSetting = true;
};
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({state: this.checkedSetting});
console.log(setting);
}
constructor(private afAuth: AngularFireAuth,private afs: AngularFirestore) {
this.settingsCollection = this.afs.collection('settings');
this.settings = this.settingsCollection.snapshotChanges().map(changes => {
return changes.map(a => {
const data = a.payload.doc.data() as Settings;
data.id = a.payload.doc.id;
this.cachedSettings = data;
return data;
});
});
}
isChecked() {
return this.checkedSetting;
}
getSettings() {
return Observable.of(this.cachedSettings);
}
updateSetting(setting: Settings) {
this.settingDoc = this.afs.doc(`settings/${setting.id}`);
this.settingDoc.update({ state: checkedSetting });
}
}

How do I let angular know a custom window event has been fired and requires update checking

I'm using a third-party library that requires me to implement my own event listener. This is done by implementing window.onGoogleYoloLoad = function() { ... }. I tried to implement it like this in my user service file:
#Injectable()
export class UserService {
public userCredentials = new EventEmitter<Credentials>();
constructor(){
window.onGoogleYoloLoad = function(credentials){
this.userCredentials.emit(credentials);
}
}
}
Then I subscribed to the event. The subscribers do get notified, but the view does not get updated. It's like angular doesn't know the event happened.
The callback is running outside the Angular zone. Move the callback to a component and call ChangeDetectorRef.detectChanges
import { Component, ChangeDetectorRef } from '#angular/core';
#Component(...)
export class MyComponent {
public userCredentials = new EventEmitter<Credentials>();
constructor(
private cd: ChangeDetectorRef,
private userService: UserService
){
window.onGoogleYoloLoad = function(credentials){
this.userService.userCredentials.emit(credentials);
this.cd.detectChanges();
}
}
}
Re-entering the Angular zone is another option: What's the difference between markForCheck() and detectChanges()
import { Injectable, NgZone } from '#angular/core';
#Injectable()
export class UserService {
public userCredentials = new EventEmitter<Credentials>();
constructor(private zone: NgZone){
window.onGoogleYoloLoad = function(credentials){
this.zone.run(() => {
this.userCredentials.emit(credentials);
})
}
}
}

Why is the service called twice in this angular 2 component?

I have here the component code, when I am subscribing to the observable the service is called twice, however if I subscribe to the Behaviorsubject it is only triggered once,
I can see on my logs that those are the result, please see my code below for my component
the method subscribeToMap() method is called on ngOninit.
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
// Observable class extensions
import 'rxjs/add/observable/of';
// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import { HeroSearchService } from './hero-search-service';
import { Hero } from './../hero';
#Component({
selector: 'hero-search',
templateUrl: './hero-search.component.html',
styleUrls: [ './hero-search.component.css' ],
providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
heroes: Observable<Hero[]>;
private searchTerms = new Subject<string>();
constructor(
private heroSearchService: HeroSearchService,
private router: Router) {}
// Push a search term into the observable stream.
search(term: string): void {
this.searchTerms.next(term);
console.log("new " + term);
}
ngOnInit(): void {
this.heroes = this.searchTerms
.debounceTime(300) // wait 300ms after each keystroke before considering the term
.distinctUntilChanged() // ignore if next search term is same as previous
.switchMap(term => {
return term // switch to new observable each time the term changes
// return the http search observable
? this.heroSearchService.search(term)
// or the observable of empty heroes if there was no search term
: Observable.of<Hero[]>([])})
.catch(error => {
// TODO: add real error handling
console.log(error);
return Observable.of<Hero[]>([]);
});
this.subscribeToMap();
}
subscribeToMap(): void{
this.heroes.subscribe(() => console.log("called twice"));
this.searchTerms.subscribe(() => console.log("called once"));
}
gotoDetail(hero: Hero): void {
let link = ['/detail', hero.id];
this.router.navigate(link);
}
}
Here is the code for my service
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Hero } from './../hero';
#Injectable()
export class HeroSearchService {
constructor(private http: Http) {}
search(term: string): Observable<Hero[]> {
console.log("service is called");
return this.http
.get(`api/heroes/?name=${term}`)
.map(response => response.json().data as Hero[]);
}
}
thank you ver much!!!
When subscription is implemented properly it has nothing to do with "unsubscribe" method, Observable, etc. This behavior is by design of Angular itself.
https://www.reddit.com/r/Angular2/comments/59532r/function_being_called_multiple_times/d95vjlz/
If you're running in development mode, it will run the function
at least twice. since in development mode it does a check, changes,
then rechecks to verify, where production mode only does the first
check, assuming you've done your quality assurance and resolved any
values the get changed post checking.
P.S. This is probably the next issue you will face to in Dev Mode :)
Angular2 change detection "Expression has changed after it was checked"
Try replacing this line:
this.heroes = this.searchTerms
With this one:
this.heroes = this.searchTerms.asObservable()
to ensure that heroes is an observable and your code can't accidentally invoke next() on it.
Your code casts hero to a Subject so you can still do next() on it.

Categories