Unable to get value of service in #CanActivate decorator in component - service

I am using Angular 2.0.0-beta.15. I am trying to use #CanActivate in the component. Below is the piece of code.
#CanActivate((next: ComponentInstruction, previous: ComponentInstruction) => {
console.log('loggedIn:' + isLoggedIn(next, previous));
console.log('isExists:' + isExists(next, previous))
})export class ParentLandingComponent {}
Now relevant piece of code in isExists.ts is as below:
import {Injector} from 'angular2/core';
import {appInjector} from './app-injector';
import {DataService} from '../services/data-services.service';
export const isExists = (next: ComponentInstruction, previous: ComponentInstruction) => {
let injector: Injector = appInjector();
let userService: UserService = injector.get(UserService);
let router: Router = injector.get(Router);
let cookieService: CookieService = injector.resolveAndInstantiate(CookieService);
dataService.isExists().subscribe(result => {
console.log('isExists:' + result);
if (result) {
console.log('result:' + result);
cookieService.removeAll();
router.navigate(['Login']);
return true;
} else {
return false;
}
});
};
I have added the following in boot.ts as well.
///<reference path="../../node_modules/angular2/typings/browser.d.ts"/>
import {bootstrap} from 'angular2/platform/browser';
import {provide} from 'angular2/core';
import {ROUTER_PROVIDERS, ROUTER_DIRECTIVES, RouteConfig, Location, LocationStrategy, HashLocationStrategy} from 'angular2/router';
import {AppComponent} from './app.component';
import {HTTP_PROVIDERS} from 'angular2/http';
import 'rxjs/Rx';
import {DataService} from './services/data-services.service';
import {StateService} from './services/current-user-state.service';
import {appInjector} from './utils/app-injector';
import {HttpClient} from './services/http-client.service';
bootstrap(AppComponent, [DataService, StateService, HttpClient, ROUTER_PROVIDERS, HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: HashLocationStrategy })])
.then(appRef => {
appInjector(appRef.injector);
});
When i running, I am getting value of isExists(next, previous) in #CanAnnotation as undefined instead of boolean value. Inside isExists.ts, I am getting correct value as result. But when i am passing boolean value based on value of result, I am getting undefined on annotation portion.Can anyone help me to know what could be the issue in this?

The lambda in CanActivate() annotation has to return boolean|Promise<boolean>. Based on your code it's not returning anything. I am guessing you will return the value from isExists(). But, also isExists() doesn't return anything that's why it's undefined. I see you are trying to return true/false from the subscribe method. But that return is asynchronous and will not be resolved as you intended.
I suggest you convert your Observable to Promise<boolean> and return it from isExists()
relevant part in isExists():
return dataService.isExists().map(result => { // change subscribe to 'map' in order to change the return type of the observable and do the other stuff
console.log('isExists:' + result);
if (result) {
console.log('result:' + result);
cookieService.removeAll();
router.navigate(['Login']);
return true;
} else {
return false;
}
}).toPromise();
#CanActivate():
#CanActivate((next: ComponentInstruction, previous: ComponentInstruction) => {
console.log('loggedIn:' + isLoggedIn(next, previous));
return isExists(next, previous);
})
export class ParentLandingComponent {}

Related

ngrx jasmine-marbles test resulting the "Received" part returning '?' question mark

I am having an issue with the following tech-stack:
Angular v8,
ionic Angular v5,
ngrx v8,
jasmine-marbles v0.8.3.
I am writing a unit test for "ngrx", in particular the "effects" part.
Following is my code snippet:
import { TestBed } from '#angular/core/testing';
import { provideMockActions } from '#ngrx/effects/testing';
import { Observable } from 'rxjs';
import { InformationEffects } from './information.effects';
import { HttpClientTestingModule, HttpTestingController } from '#angular/common/http/testing';
import { Storage } from '#ionic/storage';
import { DataService } from 'src/app/shared/services/data.service';
import { RouterTestingModule } from '#angular/router/testing';
import { cold, hot } from 'jasmine-marbles';
import { MockStore, provideMockStore } from '#ngrx/store/testing';
import {
InformationRequested,
InformationSuccess,
} from './information.actions';
describe('Information Effects', () => {
let information = {} as any;
const initialState = { information: information};
let actions$: Observable<any>;
let effects: InformationEffects;
let store: MockStore<any>;
let dataService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule],
providers: [
{
provide: Storage,
},
InformationEffects,
MockStore,
provideMockStore({ initialState }),
provideMockActions(() => actions$),
{
provide: DataService,
useValue: jasmine.createSpyObj('DataService', ['getInformation'])
}
]
});
effects = TestBed.get(InformationEffects);
store = TestBed.get(MockStore);
dataService = TestBed.get(DataService);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('INFORMATION_REQUESTED', () => {
it('should return an InformationSucess action, with the user, on success', () => {
let language = {} as any;
const action = new InformationRequested(language);
const outcome = new InformationSuccess(information);
actions$ = hot('-a-', { a: action });
const response = cold('-a|', { a: information });
const expected = cold('--b', { b: outcome });
dataService.getInformation.and.returnValue(response);
expect(effects.informationAction$).toBeObservable(expected);
});
});
});
When I run the test using "npm test", it failed at the "Received" part showing "?".
It says:
Expected: --b,
Received: --?,
Expected:
[{"frame":20,"notification":{"kind":"N","value":{"payload":{},"type":"[Information]
INFORMATION Success"},"hasValue":true}}]
Received:
[{"frame":20,"notification":{"kind":"N","value":{"payload":{},"type":"[Information]
INFORMATION Success"},"hasValue":true}}],
Please refer below figure:
enter image description here
I had searched the internet for this question mark in the "Received:" section, to no avail. I had also researched on each jasmine-marbles syntaxes, as well the (hot & cold) observable, to understand why & how to use it. Still no solution to get rid of the '?' question mark to have the unit-test being "Success". As such please help me.

reference error when I try to get a value from other slice redux toolkit?

I am trying to import a value form other slice that has some user information, any idea why I am getting this nasty error ? I read it is normal to request data from other slices, the error seem to be like the slice cannot find the store... below is my code structure, my store is at the top of my app, does this getState function works in a component only and not in slice to other slice .
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import App from './App';
import './index.css';
// Redux Tool Kit
import { store } from './app/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
import {
RootState,
store
} from './store';
import {
createSlice,
PayloadAction
} from '#reduxjs/toolkit';
export interface miscState {
dayNumber: true,
dayOfWeek: false,
};
export const miscSlice = createSlice({
name: 'misc',
initialState,
reducers: {
setDisplayDay: (state, action: PayloadAction < {
bool: boolean;type: string
} > ) => {
const {
user,
uid
} = store.getState().global.currentUser;
const setDisplay = async() => {
const docRef = doc(db, colDynamic(user)[0], uid);
await updateDoc(docRef, {
[action.payload.type]: action.payload.bool,
});
};
},
},
});
// Values
export const MiscCurrentState = (state: RootState) => state.misc;
// Action creators are generated for each case reducer function
export const {
setDisplayDay
} = miscSlice.actions;
export default miscSlice.reducer;
import { configureStore } from '#reduxjs/toolkit';
// Global
import globalReducer from './globalSlice';
// Misc
import miscReducer from './miscSlice';
export const store = configureStore({
reducer: {
global: globalReducer,
misc: miscReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
In Redux, you are not allowed to access the store from within a reducer. A reducer has to be a pure function that only reads the variables passed into it - so all information you have is the slice's own state and the action being dispatched. You are not allowed to read a global variable, have any kind of side effect or read from the global Redux store to get the data of another slice.

NGRX Entity Effects Firestore

I am following this tutorial https://www.concretepage.com/angular-2/ngrx/ngrx-entity-example. I have now Firestore as my backend. I can retrieve 'LOAD_ALL_ARTICLES' from Firestore. But my code breaks when I try to listen for the 'ADD' action. Any ideas please?!
import { Injectable } from '#angular/core';
import { Action } from '#ngrx/store';
import { Actions, Effect } from '#ngrx/effects';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/mergeMap';
import * as fromActions from '../actions/article.actions';
import * as fromReducer from '../reducers/article.reducer';
//import { ArticleService } from '../services/article.service';
import { Article } from '../models/article';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
#Injectable()
export class ArticleEffects {
constructor(
private actions$: Actions,
private afs: AngularFirestore
) {}
#Effect()
loadAllArticles$: Observable<Action> = this.actions$
.ofType(fromActions.ArticleActionTypes.LOAD_ALL_ARTICLES)
.switchMap(() =>
const ref = this.afs.collection<Article>('articles');
return ref.snapshotChanges().map(arr => {
return arr.map(action => {
const data = action.payload.doc.data() as Article;
const id = action.payload.doc.id;
return { id, ...data };
})
})
.map(data => new fromActions.LoadArticlesSuccess({ articles: data }));
//Listen for the 'ADD' action
#Effect()
addArticle$: Observable<Action> = this.actions$
.ofType(fromActions.ArticleActionTypes.ADD_ARTICLE)
.map((action: fromActions.AddArticle) => action.payload)
.switchMap( payload => {
const ref = this.afs.doc<Article>(`articles/${payload.article.id}`);
return Observable.fromPromise(ref.set(payload.article));
})
}
}
I believe you need to switchMap first:
#Effect()
addArticle$: Observable<Action> = this.actions$
.ofType(fromActions.ArticleActionTypes.ADD_ARTICLE)
.switchMap((action: fromActions.AddArticle) => of(action.payload))
.map( payload => {
const ref = this.afs.doc<Article>(`articles/${payload.article.id}`);
return Observable.fromPromise(ref.set(payload.article));
})

ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable

I'm working on autocomplete-search with angular 4. This search bar will get books information from Google Books API. It works fine when I input any search terms. But it causes an error if I remove the entire search term or input a space.This is the error I got
This is my SearchComponent.ts
import { Component, OnInit } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
#Component({
selector: 'app-admin-search',
templateUrl: './admin-search.component.html',
styleUrls: ['./admin-search.component.css']
})
export class AdminSearchComponent implements OnInit {
books: any[] = [];
searchTerm$ = new Subject<string>();
constructor (private bookService: BookService,
private http: HttpClient
) {
this.bookService.search(this.searchTerm$)
.subscribe(results => {
this.books = results.items;
});
}
ngOnInit() {
}
This is my SearchComponent.html
<div>
<h4>Book Search</h4>
<input #searchBox id="search-box"
type="text"
placeholder="Search new book"
(keyup)="searchTerm$.next($event.target.value)"/>
<ul *ngIf="books" class="search-result">
<li *ngFor="let book of books">
{{ book.volumeInfo.title }}
</li>
</ul>
</div>
This is my BookService.ts
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Book } from './book';
import { BOOKS } from './mock-books';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
#Injectable()
export class BookService {
private GoogleBookURL: string = "https://www.googleapis.com/books/v1/volumes?q=";
constructor (private http: HttpClient) { }
search(terms: Observable<string>) {
return terms.debounceTime(300)
.distinctUntilChanged()
.switchMap(term => this.searchEntries(term));
}
searchEntries(searchTerm: string) {
if (searchTerm.trim()) {
searchTerm = searchTerm.replace(/\s+/g, '+');
let URL = this.GoogleBookURL + searchTerm;
return this.http.get(URL);
}
}
}
Can someone help me out? Thanks in advance!
Your method searchEntries returns value (Observable<Response>) only if searchTerm.trim() is true (so it must return non-empty string).
There can be situation that searchEntries will return undefined instead of Obervable<Response> if trim() returns '' (empty string which is false). You can't pass undefined returned from searchEntries into .switchMap(term => this.searchEntries(term));.
For that case your code will look like this:
.switchMap(term => undefined) which is not valid construction.

Search Pipe fails when getting data from MongoDB rather than Mock Data in Angular2

Today I am trying to switch from using mock data stored in a const to using the same data stored on my local MongoDB, but I'm getting the error:
Uncaught (in promise): Error: Error in ./FoodListComponent class FoodListComponent - inline template:2:30 caused by: Cannot read property 'filter' of undefined TypeError: Cannot read property 'filter' of undefined
at SearchPipe.transform (search.pipe.ts:15)
The error occurs because of a search pipe on my *ngFor # inline template:2:30
<div *ngFor="let food of foods | searchPipe: 'mySearchTerm'">
The error message is especially odd to me because the service is returning an Observable, not a Promise.
If I remove that search pipe then every thing works fine, but I have no search functionality. It's as if the template is compiling before the data gets there. How can I correct this?
food-list.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Food } from '../../../interfaces/diet/food'
import { FoodsService } from '../../../services/foods/foods.service';
#Component({
selector: 'food-list',
templateUrl: './food-list.component.html',
styleUrls: ['./food-list.component.scss'],
providers: [ WorkingDataService, FoodsService ]
})
export class FoodListComponent implements OnInit, OnDestroy {
foods: Food[];
constructor ( private _foodsService: FoodsService) { }
ngOnInit(): void {
// this._foodsService.getFoods().subscribe(foods => this.foods = foods); // this worked fine
this._foodsService.getMongoFoods().subscribe(foods => this.foods = foods);
}
}
foods.service.ts
import { Injectable } from '#angular/core';
import { Food } from '../../interfaces/diet/food'
import { FOODS } from './mock-foods';
import { Observable } from "rxjs/Rx";
import { Http, Response } from '#angular/http';
#Injectable()
export class FoodsService {
baseURL: string;
constructor(private http: Http) {
this.baseURL = 'http://localhost:3000/'
}
getFoods(): Observable<Food[]> { // this worked with my search pipe
return Observable.of(FOODS); // I'm returning an observable to a const
}
getMongoFoods(): Observable<Food[]>{
return this.http.get(this.baseURL + 'api/foods')
.map(this.extractData)
.catch(this.handleError);
}
// ... standard response and error handling functions
}
search.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'searchPipe',
pure: false
})
export class SearchPipe implements PipeTransform {
transform(foods: any[], mySearchTerm: string): any[] {
let mySearchTerm = mySearchTerm.toUpperCase();
foods = foods.filter(food => { // The failure hits here because foods isn't defined yet
// my filter logic
});
}
}
Until your observable resolves itself, your foods array is undefined to start with in food-list.component.ts because you haven't initialised it:
foods: Food[];
if you change that to
foods: Food[] = [];
it should work.
Alternatively you can do a check for undefined at the start of your pipe, something like:
if (!foods) return foods;