I'm building an Angular Universal App. I recently added a route resolver and started seeing this weird behavior where when I run npm run dev:ssr, the page won't load until I click the reload button twice.
1st: click: browser spins and doesn't seem to timeout...
2nd click: page loads
Here is my github repo. I suspect it has something to do with my route resolver which simply fetches data from Firestore and places it in the TransferState.
Here's the resolver for convenience:
import { Inject, Injectable, PLATFORM_ID } from '#angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '#angular/router';
import { Observable, of } from 'rxjs';
import { isPlatformServer } from '#angular/common';
import { makeStateKey, TransferState } from "#angular/platform-browser";
import { Restaurant } from '../restaurants/Interfaces.model';
import { AngularFirestore } from '#angular/fire/firestore';
import { first, tap } from 'rxjs/operators';
#Injectable()
export class RestaurantResolver implements Resolve<Restaurant> {
constructor(
public afs: AngularFirestore,
private transferState: TransferState,
#Inject(PLATFORM_ID) private platformId) {
}
async resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Promise<Restaurant> {
console.log('platformId', this.platformId)
const rId = route.url[1].path;
const restaurantId = makeStateKey<Restaurant>("restaurant-" + rId);
if (this.transferState.hasKey(restaurantId)) {
console.log('has key', restaurantId)
const restaurant = this.transferState.get(restaurantId, null);
this.transferState.remove(restaurantId);
return restaurant;
}
else {
let result: Restaurant = (await this.afs.doc('restaurants/' + rId).get().toPromise()).data() as Restaurant
if (isPlatformServer(this.platformId)) {
this.transferState.set(restaurantId, result);
}
return result;
}
}
}
It turns out there is a bug in the AngularFire library where observables are not completing, this the given behavior.
Related
I am using Angular 6 with rxjs^6.0.0 and rxjs-compat^6.5.2 but I am getting the following error which I can't resolve:
rxjs__WEBPACK_IMPORTED_MODULE_1__.Observable.throw is not a function
I have tried the following solutions with no success:
"rxjs" observable.throw is not a function - Angular4
TypeError: rxjs__WEBPACK_IMPORTED_MODULE_2__.Observable.throw is not a function
https://github.com/reactivex/rxjs/issues/4070
This is the code that I'm using:
import { Injectable, Inject } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import { BehaviorSubject, throwError } from 'rxjs';
import { Observable } from 'rxjs/Observable';
import { map, tap, mergeMap } from 'rxjs/Operators';
import { Login } from "./login/login";
import { CurrentUser } from "./login/current-user";
import { Registration } from "./registrations/registration";
#Injectable()
export class AuthenticationService {
private currentUserSubject: BehaviorSubject<CurrentUser>;
public currentUser: Observable<CurrentUser>;
constructor(private httpClient: HttpClient,
#Inject("BASE_API_URL") private baseUrl: string) {
this.currentUserSubject = new BehaviorSubject<CurrentUser>(JSON.parse(localStorage.getItem('currentUser')));
this.currentUser = this.currentUserSubject.asObservable();
}
public get currentUserValue(): CurrentUser {
return this.currentUserSubject.value;
}
signIn<T>(login: Login) {
return this.httpClient.post<T>(`${this.baseUrl}api/authentication/login`, login)
.pipe(mergeMap(loginResult => {
return this.httpClient.get<Registration>(`${this.baseUrl}api/users/${loginResult.user.id}/registration`)
.pipe(map(registration => {
if (registration.registrationStatusId == 1)
return throwError('Registration is pending approval.');
else if (registration.registrationStatusId == 3)
return throwError('Registration has been rejected.');
let currentUser = new CurrentUser();
currentUser.identity = registration;
currentUser.identity.user = loginResult.user;
currentUser.token = loginResult.token;
currentUser.refreshToken = loginResult.refreshToken;
localStorage.setItem('currentUser', JSON.stringify(currentUser));
this.currentUserSubject.next(currentUser);
}));
}));
}
}
Your import code is correct but I think you should use same version for rxjs and rxjs-compact so update your package.json
"rxjs": "^6.5.2",
"rxjs-compat": "^6.5.2"
Then delete package-lock.json then run npm install again
Hi i'm new in Angular 4 and I want to use it to build a WordPress theme using the wp-api. I start with the ng-wp-theme but I and all its working fine, but I need that hen a new post is publish the post list page updates itself without reload the page. I saw some tutorials about the http services in angular but I dont find any solution to this, maybe its a Wordpress api issue and not the Angular part.
here is the service:
import { Injectable } from '#angular/core';
import { HttpClient } from "#angular/common/http";
import { Observable } from 'rxjs/Observable';
import { Post } from './post';
import { environment } from '../../environments/environment';
#Injectable()
export class PostsService {
private _wpBase = environment.wpBase;
constructor(private http: HttpClient) { }
getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(this._wpBase + 'posts');
}
getPost(slug: string): Observable<Post[]> {
return this.http.get<Post[]>(this._wpBase + `posts?slug=${slug}`);
}
}
and the controller:
import { Component, OnInit } from '#angular/core';
import { Post } from '../post';
import { PostsService } from '../posts.service';
import { Router } from '#angular/router';
import { HttpErrorResponse } from '#angular/common/http';
#Component({
selector: 'app-post-list',
templateUrl: './post-list.component.html',
styleUrls: ['./post-list.component.css'],
providers: [PostsService]
})
export class PostListComponent implements OnInit {
public posts: Post[];
constructor( private postsService: PostsService, private router: Router ) {}
ngOnInit() {
this.postsService.getPosts().subscribe(
(posts: Post[]) => this.posts = posts,
(err: HttpErrorResponse) => err.error instanceof Error ? console.log('An error occurred:', err.error.message) : console.log(`Backend returned code ${err.status}, body was: ${err.error}`));
}
selectPost(slug) {
this.router.navigate([slug]);
}
}
i'm struggling to find a solution to this problem. I googled for hours but couldn't solve.
I need to change the menu item in my navbar template showing login vs logout when the user logs in and out.
I have 2 services, the authService which let the user login/logout and the sessionService which provides some basic functions to check if the session is set or not.
I want that every time i logIn or logout the user (so i update the localStorage in sessionService) my components which use this get updated too.
I tried with .subscribe, .map but cannot make this work. The only thing that make this work is call this.loggedIn.next(this._sessionService.isSetUserSession()); in login and logout methods.
Please, what am i doing wrong?
navbar.component.html
<div class="dropdown-menu" aria-labelledby="dropdownAccount">
<ng-template *ngIf="(loggedIn$ | async); else elseDiv;">
<a class="nav-link" href="javascript:void(0)" (click)="logout()">LOGOUT</a>
</ng-template>
<ng-template #elseDiv>
<a class="nav-link" href="javascript:void(0)"(click)="login(...);">
LOGIN
</a>
</ng-template>
</div>
navbar.component.ts
import {Component, OnInit} from '#angular/core';
import {AuthService} from '../services/auth.service';
#Component({
moduleId: module.id,
selector: 'app-nav-bar',
templateUrl: 'navbar.component.html',
styleUrls: ['./navbar.component.css'],
})
export class NavbarComponent implements OnInit {
isNavbarCollapsed = true;
loggedIn$: any;
constructor(
private authService: AuthService) {
}
ngOnInit() {
this.loggedIn$ = this.authService.isLoggedIn;
}
}
auth.service.ts
import {Injectable} from '#angular/core';
import {Http, Response} from '#angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import {IUser} from '../entities/user';
import {SessionService} from './session.service';
import {Router} from '#angular/router';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class AuthService {
private loggedIn: Subject<boolean> = new Subject<boolean>();
get isLoggedIn() {
return this.loggedIn.asObservable();
}
constructor(private _http: Http,
private _sessionService: SessionService,
private _router: Router) {
this.loggedIn.next(this._sessionService.isSetUserSession());
}
login(user: IUser) {
return this._http.get('assets/api/responseSuccess.json?email=' + user.email + '&password=' + user.password)
.map((responseLogin => {
const jsonResponse = responseLogin.json();
if (jsonResponse.response === 'success') {
const userResponse: IUser = jsonResponse.user;
this._sessionService.setUserSession(userResponse);
//this.loggedIn.next(this._sessionService.isSetUserSession()); ==> This makes this works but i don't want fo call this every time i change the session, i just want that the session syncs automatically
return true;
} else {
console.log("error loggin in");
return false;
}
}));
}
logout() {
this._sessionService.clearUserSession();
// this.loggedIn.next(this._sessionService.isSetUserSession()); =>> Same here
return this._router.navigate(['/']);
}
}
session.service.ts
import {Injectable} from '#angular/core';
import {IUser} from '../entities/user';
import {isNullOrUndefined} from 'util';
import {Subject} from "rxjs/Subject";
#Injectable()
export class SessionService {
isSetUserSession(): boolean {
return !!localStorage.getItem('user');
}
clearUserSession() {
localStorage.removeItem('user');
}
setUserSession(user: IUser) {
localStorage.setItem('user', JSON.stringify(user));
}
}
This can be achieved without using subject instead use Shared service
Have the variable in service as below,
export class DataService {
isLoggedIn: boolean = false;
}
In the component get and set it as below,
get data():string {
return this.dataService.isLoggedIn;
}
set data(value: string) {
this.dataService.isLoggedIn = value;
}
Update 1 : Alternatively it can updated via another service also as below,
#Injectable()
export class AuthService {
constructor(public dataService: DataService) { }
update(value){
console.log(value)
this.dataService.isLoggedIn = value;
}
}
LIVE DEMO
Please make following changes.
Use BehaviorSubject instead of Subject.
A BehaviorSubject holds one value. When it is subscribed it emits the value immediately. A Subject doesn't hold a value. When you set the subject variable, it doesn't reflect in the nav component.
auth.service.ts
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
return this.loggedIn.asObservable();
}
Set the loggedIn variable in login and logout functions.
login(user: IUser) {
return this._http.get('assets/api/responseSuccess.json?email=' + user.email + '&password=' + user.password)
.map((responseLogin => {
const jsonResponse = responseLogin.json();
if (jsonResponse.response === 'success') {
const userResponse: IUser = jsonResponse.user;
this._sessionService.setUserSession(userResponse);
this.loggedIn.next(this._sessionService.isSetUserSession());
return true;
} else {
console.log("error loggin in");
return false;
}
}));
}
logout() {
this._sessionService.clearUserSession();
this.loggedIn.next(this._sessionService.isSetUserSession());
return this._router.navigate(['/']);
}
in navbar.component.ts
replace loggedIn$: any; by
isLoggedIn$: Observable<boolean>;
An explanation as follows.
The BehaviorSubject keeps the latest value cached which is set when user log ins or logs out. So when an Observer subscribes to the isLoggedIn(), the cached value is going to be emitted right away, depending on user is signed in or not.
This code will work for sure, as I am using in my current project.
I have a web application where there is a JWT token passed to the admin service. This JWT comes from the query URL as there is a redirect from another application. The constructor in the service checks for that URL and set the token value to it (if that params is there).
The problem I am facing is that the canActivateGuard fires too early. When this is called the observable to get the JWT in the service is not solved yet, so the JWT is always not there when the guard is being fired.
I have figure out that to make this work isLoggedIn() in the AdminService has to become an observable that listen to the changes in the URL, and the canActivate() in the guard has to subscribe to it, but can not make this works.
The below code is what I got so far
// Admin Service
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import 'rxjs/Rx';
import {Router, ActivatedRoute, Params} from '#angular/router';
#Injectable()
export class AdminService {
token: string;
constructor(private activatedRoute: ActivatedRoute) {
activatedRoute.queryParams.subscribe(
(params) => {
console.log('queryParams', params);
if(localStorage.getItem('jwt')) {
this.token = localStorage.getItem('jwt');
}
else if(params['jwt']) {
localStorage.setItem('jwt', params['jwt']);
this.token = params['jwt'];
}
});
}
// Check that JWT is in local storage and valid
isLoggedin() {
return (localStorage.getItem('jwt') !== null && localStorage.getItem('jwt') !== 'undefined');
}
}
// Can Activate guard
// Note that this.authService.isLoggedIn() is called before the set JWT in the service is solved
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AdminService,
private router: Router
) {
}
canActivate() {
if (this.authService.isLoggedin()) {
console.log('all ok, proceed navigation to routed component')
return true;
}
else {
// start a new navigation to redirect to login page
this.router.navigate(['/unauthorized']);
return false;
}
}
}
using ActivatedRouteSnapshot and RouterStateSnapshot your problem will be get resolve, and you don't need to subscribe for JWT in your service.
Here is a sample of my code which i had used in my Angular2 application.
auth-guard.ts
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '#angular/router';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { AuthCookie } from '../shared/services/auth-cookies-handler';
#Injectable()
export default class AuthGuard implements CanActivate {
constructor(private router: Router, private _authCookie: AuthCookie) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
if (this._authCookie.getAuth()) {
return true;
}
else {
this.router.navigate(['/login']);
return false;
}
}
}
Hope this will help you.
I'm having trouble implementing a service that loads the data (gyms array) once, then allows all other components to use it, without making other HTTP requests.
My application works fine if the user started at the title page and loaded all the data, but when I go to a specific detail page (.../gym/1) and reload the page, the object isn't in the service array yet. How can I make the component that tries to access the service array wait until the data is loaded? More specifically, how can I delay the call of gymService.getGym(1) in the GymComponent until the getAllGymsFromBackEnd() function is done populating the array?
I've read about resolvers but my tinkering led me nowhere.
Any help would be appreciated.
This is the code I was working on:
Service:
import {Injectable} from "#angular/core";
import {Gym} from "../objects/gym";
import {BaseService} from "./base.service";
import {Http, Response} from "#angular/http";
import {HttpConstants} from "../utility/http.constants";
#Injectable()
export class GymService extends BaseService {
private gyms: Gym[] = [];
constructor(protected http: Http, protected httpConstants: HttpConstants) {
super(http, httpConstants);
this.getAllGymsFromBackEnd();
}
getAllGymsFromBackEnd() {
return super.get(this.httpConstants.gymsUrl).subscribe(
(data: Response) => {
for (let gymObject of data['gyms']) {
this.gyms.push(<Gym>gymObject);
}
}
);
}
getGyms() {
return this.gyms;
}
getGym(id: number) {
return this.gyms.find(
gym => gym.id === id
)
}
}
Component:
import {Component, OnDestroy, AfterViewInit, OnInit} from "#angular/core";
import {ActivatedRoute} from "#angular/router";
import {Subscription} from "rxjs";
import {Gym} from "../../objects/gym";
import {GymService} from "../../services/gym.service";
declare var $:any;
#Component({
selector: 'app-gym',
templateUrl: './gym.component.html',
styleUrls: ['./gym.component.css']
})
export class GymComponent implements OnInit, OnDestroy, AfterViewInit {
private subscription: Subscription;
private gym: Gym;
constructor(private activatedRoute: ActivatedRoute,
private gymService: GymService
) {}
ngOnInit(): void {
this.subscription = this.activatedRoute.params.subscribe(
(param: any) => {
this.gym = this.gymService.getGym(parseInt(param['id']));
}
);
}
ngAfterViewInit(): void {
$( document ).ready(function() {
$('.carousel').carousel();
});
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
You can use Resolver as well. Check it here https://angular.io/docs/ts/latest/api/router/index/Resolve-interface.html or use Observable. So the private gym: Gym; will become private gym$:Observable<Gym>;, and in your template, use async pipe to get the data.