NgxLocalStorage Doesn't Retrieve Value - visual-studio

I'm learning how to use the Visual Studio 2017 SPA Template for Angular 2.
For this exercise I would just like my HomeComponent to display the name of the logged on user stored in local storage (NgxLocalStorage) after logging in on my AppLoginComponent. https://www.npmjs.com/package/ngx-localstorage
I've researched this issue and I believe I'm going down the right track but for some reason my HomeComponent doesn't see the key/value pair in localStorage. However, I can see it in Chrome's Developer Tools after I set it in login().
NgxLocalStorage has a method called get, not getItem but it appears to work the same way as getItem. Unfortunately it's not retrieving my value.
I'm pretty new to Angular 2, I'm sure I'm just missing something somewhere, please help.
I have imported NgxLocalStorageModule into NgModule in app.module.shared:
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { RouterModule } from '#angular/router';
import { NgxLocalStorageModule } from 'ngx-localstorage';
import { AppComponent } from './components/app/app.component';
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { AppLoginComponent } from './components/applogin/applogin.component';
import { FacebookService, FacebookModule } from 'ngx-facebook/dist/esm/index';
#NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
AppLoginComponent
],
imports: [
CommonModule,
HttpModule,
FormsModule,
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'applogin', component: AppLoginComponent },
{ path: '**', redirectTo: 'home' }
]),
FacebookModule.forRoot(),
NgxLocalStorageModule.forRoot()
],
providers: [FacebookService, NgxLocalStorageModule]
})
export class AppModuleShared {
}
In my HomeComponent I have:
import { Component } from '#angular/core';
import { LocalStorageService } from 'ngx-localstorage';
#Component({
selector: 'home',
templateUrl: './home.component.html'
})
export class HomeComponent {
currentUser: string;
constructor(private localStorage: LocalStorageService) {
this.currentUser = JSON.parse(localStorage.get('currentUser') || '');
}
}
In AppLoginComponent I have:
import { Component, NgZone } from '#angular/core';
import { FacebookService, InitParams, LoginResponse } from 'ngx-facebook/dist/esm/index';
import { LocalStorageService } from 'ngx-localstorage';
#Component({
selector: 'applogin',
templateUrl: './applogin.component.html'
})
export class AppLoginComponent {
public loggedIn = false;
name = "";
constructor(private _ngZone: NgZone, private fb: FacebookService, localStorage: LocalStorageService) {
let initParams: InitParams = {
appId: '123456789',
xfbml: true,
version: 'v2.8'
};
fb.init(initParams);
}
login() {
var self = this;
this.fb.login()
.then((res: LoginResponse) => {
if (res.authResponse) {
this.fb.api('/me')
.then((res: any) => {
self._ngZone.run(() => {
self.name = res.name;
self.loggedIn = true;
localStorage.setItem('currentUser', res.name);
});
});
} else {
alert('Not authorized.');
}
})
.catch();
}

I have created plunker where everything works. You press login button it will navigate to different component and show user in console the only difference from your code i used
this.localStorage.set('item', item);
and this.localStorage.get('item');
also in your code
this.fb.api('/me')
.then((res: any) => {
self._ngZone.run(() => {
self.name = res.name;
self.loggedIn = true;
localStorage.setItem('currentUser', res.name);
});
});
you can't use like this services outside constructor and don't use self you need to add 'this'. and in you constructor prefix localStorage with private
and do initialization better in OnInit hook.
import { Component, NgZone, OnInit } from '#angular/core';
import { FacebookService, InitParams, LoginResponse } from 'ngx-facebook/dist/esm/index';
import { LocalStorageService } from 'ngx-localstorage';
#Component({
selector: 'applogin',
templateUrl: './applogin.component.html'
})
export class AppLoginComponent implements OnInit {
public loggedIn = false;
name = "";
constructor(private _ngZone: NgZone, private fb: FacebookService, private localStorage: LocalStorageService) {
}
ngOnInit() {
let initParams: InitParams = {
appId: '123456789',
xfbml: true,
version: 'v2.8'
};
fb.init(initParams);
}
login() {
this.fb.login()
.then((res: LoginResponse) => {
if (res.authResponse) {
this.fb.api('/me')
.then((res: any) => {
this._ngZone.run(() => {
this.name = res.name;
this.loggedIn = true;
this.localStorage.set('currentUser', res.name);
});
});
} else {
alert('Not authorized.');
}
})
.catch();
}
and in app.module.shared.ts remove this line
providers: [FacebookService, NgxLocalStorageModule]
cause forRoot is importing them already. should be like this
#NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
AppLoginComponent
],
imports: [
CommonModule,
HttpModule,
FormsModule,
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'applogin', component: AppLoginComponent },
{ path: '**', redirectTo: 'home' }
]),
FacebookModule.forRoot(),
NgxLocalStorageModule.forRoot()
]
})
export class AppModuleShared {
}
and the last
import { FacebookService, FacebookModule } from 'ngx-facebook/dist/esm/index';
try to use import without dist
import { FacebookModule } from 'ngx-facebook';

The input has to be a string. You can put in some mock data like
localStorage.setItem('currentUser', 'TrevorBrooks');
and retrieve it via get to be sure there is a item saved. And check what data type you are sending. Is it a user object or is it just the name?
Greetings

You should use NgOnInit, is the best approach for your problem than using the constructor since the constructor is for initialization, dependency injection among others.. so, what could be happening is that the data is not yet available by the time you request it. Besides in the npmjs.com page they clearly add an example using ngOnInit so i guess they saw this issue coming.
In your components, do import { .., OnInit, .. } from '#angular/core';
`
so you'd have something like:
import { Component, NgZone, OnInit } from '#angular/core';
and in your component export class:
export class AppLoginComponent implements OnInit{
ngOnInit() {
//write your code here
}

1.Make sure you have been installed the local storage module from npm
npm install --save angular2-localstorage
2.Import the WebStorageModule in your app module:
import {Component} from "angular2/core";
import {WebStorageModule, LocalStorageService} from "angular2-localstorage";
#NgModule({
import: [WebStorageModule]
#Component({
providers: [LocalStorageService]
})
export class AppModule {}
2.Use the LocalStorage decorator
import {LocalStorage, SessionStorage} from "angular2-localstorage/WebStorage";
class MySuperComponent {
#LocalStorage() public lastSearchQuery:Object = {};
#LocalStorage('differentLocalStorageKey') public lastSearchQuery:Object = {};
}
Example
#Component({
selector: 'app-login',
template: `
<form>
<div>
<input type="text" [(ngModel)]="username" placeholder="Username" />
<input type="password" [(ngModel)]="password" placeholder="Password" />
</div>
<input type="checkbox" [(ngModel)]="rememberMe" /> Keep me logged in
<button type="submit">Login</button>
</form>
`
})
class AppLoginComponent {
//here happens the magic. `username` is always restored from the localstorage when you reload the site
#LocalStorage() public username:string = '';
public password:string;
//here happens the magic. `rememberMe` is always restored from the localstorage when you reload the site
#LocalStorage() public rememberMe:boolean = false;
}
View
#Component({
selector: 'admin-menu',
template: `
<div *ngFor="#menuItem of menuItems() | mapToIterable; #i = index">
<h2 (click)="hiddenMenuItems[i] = !!!hiddenMenuItems[i]">
{{i}}: {{category.label}}
</h2>
<div style="padding-left: 15px;" [hidden]="hiddenMenuItems[i]">
<a href>Some sub menu item 1</a>
<a href>Some sub menu item 2</a>
<a href>Some sub menu item 3</a>
</div>
</div>
`
})
class AdminMenuComponent {
public menuItems = [{title: 'Menu1'}, {title: 'Menu2'}, {title: 'Menu3'}];
//here happens the magic. `hiddenMenuItems` is always restored from the localstorage when you reload the site
#LocalStorage() public hiddenMenuItems:Array<boolean> = [];
//here happens the magic. `profile` is always restored from the sessionStorage when you reload the site from the current tab/browser. This is perfect for more sensitive information that shouldn't stay once the user closes the browser.
#SessionStorage() public profile:any = {};
}
For more Clarification refer this link link

Related

NullInjectorError: No provider for InjectionToken Window

I have been trying to debug this issue but I have had no success. I believe it's a lack of import of a nebular service or provider but I am not sure which one or where to find it.
my search.component.spec.ts
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { NgxsModule, Store } from '#ngxs/store';
import { of } from 'rxjs';
import { SearchComponent } from './search.component';
import { NbAutocompleteModule, NbInputModule, NbOverlay, NbOverlayService, NbTriggerStrategyBuilderService, NbWindowService, NB_WINDOW } from '#nebular/theme';
import { InjectionToken } from '#angular/core';
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
let store: Store;
let windowMock: NbWindowService = { } as any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SearchComponent ],
imports: [NgxsModule.forRoot([]), HttpClientTestingModule, NbAutocompleteModule, NbInputModule ],
providers: [ NbWindowService, NbTriggerStrategyBuilderService,
{provide: NB_WINDOW, useClass: InjectionToken }
,
{ provide: NbOverlayService, useClass: NbOverlay }]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
const store:Store = TestBed.inject(Store);
spyOn(store, 'select').and.returnValue(of(null));
spyOn(store, 'selectSnapshot').and.returnValue(null);
expect(component).toBeTruthy();
});
});
my search.component.html
<input #autoInput
nbInput
type="text"
(input)="onChange()"
placeholder="Search for companies"
[nbAutocomplete]="auto"
class= "search__input" />
<nb-autocomplete #auto (selectedChange)="onSelectionChange($event)">
<nb-option *ngFor="let option of filteredOptions$ | async" [value]="option">
{{ option }}
</nb-option>
</nb-autocomplete>
It's the bog-standard search component provided by nebular https://akveo.github.io/nebular/docs/components/autocomplete/overview#nbautocompletedirective
SearchComponent > should create
NullInjectorError: R3InjectorError(DynamicTestModule)[InjectionToken Window -> InjectionToken Window]:
NullInjectorError: No provider for InjectionToken Window!
error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'InjectionToken Window', 'InjectionToken Window' ] })
at <Jasmine>
at NullInjector.get (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:915:1)
at R3Injector.get (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:11081:1)
at R3Injector.get (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:11081:1)
at NgModuleRef$1.get (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:24198:1)
at Object.get (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:22101:1)
at getOrCreateInjectable (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:3921:1)
at Module.ɵɵdirectiveInject (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:13752:1)
at NodeInjectorFactory.NbMenuComponent_Factory [as factory] (http://localhost:63217/_karma_webpack_/node_modules/#nebular/theme/__ivy_ngcc__/fesm2015/index.js:9693:120)
at getNodeInjectable (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:4029:1)
at instantiateRootComponent (http://localhost:63217/_karma_webpack_/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:7825:1)

How to set state or dispatch actions from a navigator (ex : cypress testing)

A good practice given by Cypress (e2e testing) is to set the state of the app programmatically rather than using the UI. This of course makes sense.
On this video https://www.youtube.com/watch?v=5XQOK0v_YRE Brian Mann propose this solution to expose a Redux store :
Is there any possibility with NGXS to have access to the different state programmatically during testing ? An example is for the login process : dispatching directly a Login action or setting the store with the access token, to be logged in before any test, would be nice.
This cofnfiguration works for me:
in app folder in model:
export interface IWindowCypress {
Cypress: {
__store__: Store;
};
}
in app.module.ts:
import {BrowserModule} from '#angular/platform-browser';
import {NgModule} from '#angular/core';
import {NgxsModule, Store} from '#ngxs/store';
import {AppComponent, IWindowCypress} from './app.component';
import {ZooState} from './state/zoo.state';
import {NgxsReduxDevtoolsPluginModule} from '#ngxs/devtools-plugin';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule, NgxsModule.forRoot([ZooState], {}),
NgxsReduxDevtoolsPluginModule.forRoot()
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(protected store: Store) {
const windowSore: IWindowCypress = window as unknown as IWindowCypress;
if (windowSore.Cypress) {
console.log('ustawiłem store');
windowSore.Cypress.__store__ = store;
}
}
}
using in app component:
import {Component} from '#angular/core';
import {Store} from '#ngxs/store';
import {FeedAnimals} from './state/zoo.state';
/// <reference types="Cypress" />
export interface IWindowCypress {
Cypress: {
__store__: Store;
};
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'cypress-ngxs';
constructor() {
const windowSore: IWindowCypress = window as unknown as IWindowCypress;
if (windowSore.Cypress) {
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
}
}
}
using in cypress spec:
/// <reference types="Cypress" />
import {Store} from '#ngxs/store';
import {IWindowCypress} from 'src/app/app.component';
import {FeedAnimals, ZooState} from '../../../src/app/state/zoo.state';
import {Observable} from 'rxjs';
describe('My Second Test Suite', () => {
it('My FirstTest case', () => {
cy.visit(' http://localhost:4200/ ');
cy.get('.content > :nth-child(2)').should(item => {
const windowSore: IWindowCypress = window as unknown as IWindowCypress;
if (windowSore.Cypress) {
// get store
const store: Store = windowSore.Cypress.__store__;
// declare observable
const myObs: Observable<boolean> = store.select(ZooState.zoo$);
// subscribe
myObs.pipe().subscribe((feed) => console.log('from subscribe: ', feed));
// make some dispatch
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
}
});
});
});
and zoo state:
import {Injectable} from '#angular/core';
import {Action, Selector, State, StateContext} from '#ngxs/store';
export class FeedAnimals {
static readonly type = '[Zoo] FeedAnimals';
}
export interface ZooStateModel {
feed: boolean;
}
#State<ZooStateModel>({
name: 'zoo',
defaults: {
feed: false
}
})
#Injectable()
export class ZooState {
#Selector()
static zoo$(state: ZooStateModel): boolean {
return state.feed;
}
#Action(FeedAnimals)
feedAnimals(ctx: StateContext<ZooStateModel>): void {
console.log('fedeeeeeed');
const state = ctx.getState();
ctx.setState({
...state,
feed: !state.feed
});
}
}

Nativescript + angular : blank page on tab view after upgrading to 8.2.0 without any error

Environment
CLI: 6.0.3
Cross-platform modules:6.0.0
Android Runtime:6.0.2
iOS Runtime:6.0.2
Plugin(s):
NativeScript-Angular:8.2.0
Angular:8.2.0
After upgrading to the latest cli and nativescript-angular I get a blank white screen when I start the app which opens the page tab based, but when I try to output something using the component ts file I can get it on the console. The problem is only at the template level (I get a blank page).
this is my app.modules.ts file:
import { NgModule, NO_ERRORS_SCHEMA } from "#angular/core";
import { CoreModule } from "./core/core.module";
import { SharedModule } from "./shared/shared.module";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { NativeScriptCommonModule } from "nativescript-angular/common";
import { TNSImageModule } from 'nativescript-image/angular';
import * as applicationModule from "tns-core-modules/application";
import * as imageModule from "nativescript-image";
declare var GMSServices: any;
if (applicationModule.android) {
applicationModule.on(applicationModule.launchEvent, () => {
console.log('initialize pipeline');
imageModule.initialize();
});
} else {
GMSServices.provideAPIKey("*********");
}
// Uncomment and add to NgModule imports if you need to use two-way binding
// import { NativeScriptFormsModule } from "nativescript-angular/forms";
// Uncomment and add to NgModule imports if you need to use the HttpClient wrapper
// import { NativeScriptHttpClientModule } from "nativescript-angular/http-client";
#NgModule({
bootstrap: [AppComponent],
imports: [NativeScriptCommonModule, CoreModule, SharedModule, TNSImageModule, AppRoutingModule],
declarations: [AppComponent],
providers: [],
schemas: [NO_ERRORS_SCHEMA]
})
/*
Pass your application module to the bootstrapModule function located in main.ts to start your app
*/
export class AppModule {}
this is the app-routing.modules.ts
import { NgModule } from "#angular/core";
import { NativeScriptRouterModule } from "nativescript-angular/router";
//import { hasKey } from "tns-core-modules/application-settings";
import { Routes } from "#angular/router";
//const homePath = (hasKey('skipLoginScreen') ? 'home/tabs':'auth/login');
const routes: Routes = [
{
path: "",
redirectTo: "home",
pathMatch: "full"
},
{
path: "home",
loadChildren: "~/app/home/home.module#HomeModule"
},
{
path: "products",
loadChildren: "~/app/products/products.module#ProductsModule"
},
{
path: "auth",
loadChildren: "~/app/auth/auth.module#AuthModule"
},
{
path: "account",
loadChildren: "~/app/account/account.module#AccountModule"
},
{
path: "cart",
loadChildren: "~/app/cart/cart.module#CartModule"
}
];
#NgModule({
imports: [NativeScriptRouterModule.forRoot(routes, { enableTracing: true } )],
exports: [NativeScriptRouterModule]
})
export class AppRoutingModule {}
this is my home-routing.module.ts:
import { NgModule } from "#angular/core";
import { Routes } from "#angular/router";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { TabsComponent } from "./tabs.component";
import { HomeComponent } from "./home-tab/home.component";
import { CategoriesComponent } from "./categories-tab/categories.component";
import { InfoComponent } from "./info-tab/info.component";
import { LocationsComponent } from "./locations-tab/locations.component";
import { AccountComponent } from "./account-tab/account.component";
export const COMPONENTS = [TabsComponent, HomeComponent, CategoriesComponent, InfoComponent, LocationsComponent, AccountComponent];
const routes: Routes = [
{
path: "",
redirectTo: "tabs",
pathMatch: "full"
},
{
path: "tabs",
component: TabsComponent,
children: [{ path: "home", component: HomeComponent, outlet: "homeTab" }, { path: "categories", component: CategoriesComponent, outlet: "categoriesTab" }, { path: "info", component: InfoComponent, outlet: "infoTab" }, { path: "locations", component: LocationsComponent, outlet: "locationsTab" }, { path: "account", component: AccountComponent, outlet: "accountTab" }]
}
];
#NgModule({
imports: [NativeScriptRouterModule.forChild(routes)],
exports: [NativeScriptRouterModule]
})
export class HomeRoutingModule {}
this is my home.module.ts:
import { NgModule, NO_ERRORS_SCHEMA } from "#angular/core";
import { HomeRoutingModule, COMPONENTS } from "./home-routing.module";
import { SharedModule } from "../shared/shared.module";
import { PushNotificationsService } from './../core/services/push-notifications.service';
// Uncomment and add to NgModule imports if you need to use two-way binding
// import { NativeScriptFormsModule } from "nativescript-angular/forms";
// Uncomment and add to NgModule imports if you need to use the HttpClient wrapper
// import { NativeScriptHttpClientModule } from "nativescript-angular/http-client";
#NgModule({
imports: [SharedModule, HomeRoutingModule],
providers: [PushNotificationsService],
declarations: [...COMPONENTS],
schemas: [NO_ERRORS_SCHEMA]
})
/*
Pass your application module to the bootstrapModule function located in main.ts to start your app
*/
export class HomeModule {}
this is my tabs.component.ts:
import { Component, OnInit } from "#angular/core";
import { Page } from "tns-core-modules/ui/page";
import { RouterExtensions } from "nativescript-angular/router";
import { DataService } from "../core/services/data.service";
import { ActivatedRoute } from "#angular/router";
#Component({
selector: "tabs",
moduleId: module.id,
templateUrl: "./tabs.component.html"
})
export class TabsComponent implements OnInit {
selectedIndex: number = 4;
constructor(private page: Page, private activeRoute: ActivatedRoute, private dataService: DataService, private routerExt: RouterExtensions) {}
ngOnInit(): void {
this.page.actionBarHidden = true;
this.routerExt.navigate([{ outlets: { homeTab: ["home"], infoTab: ["info"], categoriesTab: ["categories"], accountTab: ["account"], locationsTab: ["locations"] } }], { relativeTo: this.activeRoute });
this.dataService.getActivatedTab().subscribe(index => {
this.selectedIndex = index;
});
}
onTabChanged(args) {
setTimeout(() => {
this.dataService.setActivatedTab(args.newIndex);
}, 30);
}
}

How can I be sure a constructor function only runs once?

Is there any way for this code in the constructor of my service TranslatorService is calling just one time for all application or on demande, and not all time homecomponent is load ????
this.translations$ = translationsCollection.snapshotChanges().map(actions => {
return actions.map(a => {
...
})
translation class for my translate retrive from firestore ex:
{
"WELCOME": {
"FR": "bienvenue",
"GB": "Welcome"
}
}
export interface Translation {
[code: string]: TranslationInfo;
}
export interface TranslationInfo {
[language: string]: string;
}
my core module
import { NgModule } from '#angular/core';
import { AuthService } from './auth.service';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFirestoreModule } from 'angularfire2/firestore';
import { TranslatorService } from '../services/translator.service';
#NgModule({
imports: [
AngularFireAuthModule,
AngularFirestoreModule
],
providers: [AuthService, TranslatorService]
})
export class CoreModule { }
injected in appmodule
#NgModule({
declarations: [
...
],
imports: [
...
CoreModule,
...
],
providers: [AuthGuard],
bootstrap: [AppComponent]
})
export class AppModule { }
my translator service
#Injectable()
export class TranslatorService {
translationsCollection: AngularFirestoreCollection<Translation>;
translations$: Observable<any>;
public translations: Translation[];
constructor(private afs: AngularFirestore) {
var translationsCollection = this.afs.collection("translations");
this.translations$ = translationsCollection.snapshotChanges().map(actions => {
return actions.map(a => {
...
})
});
my component
export class HomeComponent implements OnInit {
translations$: Observable<any>;
constructor(private translator: TranslatorService) { }
ngOnInit() {
this.translations$ = this.translator.translations$;
}
my component view
<div *ngFor="let translation of translations$|async">
<pre>{{translation | json}}</pre>
</div>
Just use a singleton pattern: move the code to a static function and store its result in a static field. The constructor just calls the static function which will check the field before running its code:
export class TranslatorService {
translationsCollection: AngularFirestoreCollection<Translation>;
translations$: Observable<any>;
static tCollection : AngularFirestoreCollection<Translation>;
static t$: Observable<any>;
static initialize(afs: AngularFireStore) : void {
if (TranslatorService.tCollection == null) {
TranslatorService.tCollection = afs.collection("translations");
TranslatorService.t$ = TranslatorService.tCollection.snapshotChanges().map(actions => actions.map(a => ...));
}
}
public translations: Translation[];
constructor(private afs: AngularFirestore) {
TranslatorService.initialize(this.afs);
this.translations$ = TranslatorService.t$;
this.translationsCollection = TranslatorService.tCollection;
}
}
I have done something similar in one of my projects. You just need to move your translation code one level above. i.e.
Make a component called translation component which wraps all the components and put your logic in it. That way it will be loaded only once through the application life cycle.
You route config should then look something as follows:
const routes: Routes = [
{
path: '', component: LanguageComponent,
children: [
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
]
}
];
Basically all your application is wrapped into Language component.

Angular 2 unit test for component

I am using ng2 with webpack 2.
I cant figure out how to test component functions
Here is my component
import { Component, OnInit } from '#angular/core';
import { GlobalDataService } from '../global.service';
import { Router } from '#angular/router';
#Component({
selector: 'login',
templateUrl: './login.component.html'
})
export class LoginComponent {
constructor(private gd: GlobalDataService, private router: Router) { }
login(): void {
this.gd.shareObj['role'] = 'admin';
this.router.navigateByUrl('/login');
}
}
I would like to test login() function and see, if this.gd.shareObj['role'] = 'admin'; is truly set as admin.
What could .spec.ts file look like?
I would do it as follows:
class RouterStub {
navigateByUrl(url: String) { return url; }
}
class GlobalDataServiceStub {
shareObj: any = {};
}
describe('LoginComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent],
providers: [
{ provide: GlobalDataService, useClass: GlobalDataServiceStub },
{ provide: Router, useClass: RouterStub }
]
});
fixture = TestBed.createComponent(LoginComponent);
comp = fixture.componentInstance;
});
it('should set role to admin',
inject([GlobalDataService], (gd: GlobalDataService) => {
comp.login();
expect(gd.shareObj['role']).toBe('admin');
})
);
});
Plunker Example

Resources