Undefined config when injecting a config service - javascript

This is a follow-up of my previous post. I've been debugging this issue for quite a while now and even though I haven't fixed it, I made some discoveries so maybe someone will be able to help.
Here's the whole setup:
app-config.json (/src/assets/):
{
"apiUrl": "localhost:8080"
}
app-config.service.ts (/src/app/):
import {Injectable, Injector} from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class AppConfigService {
private appConfig: any;
constructor (private injector: Injector) { }
loadAppConfig() {
let http = this.injector.get(HttpClient);
return http.get('/assets/app-config.json')
.toPromise()
.then(data => {
this.appConfig = data;
})
}
get config() {
return this.appConfig;
}
}
app.module.ts (/src/app/):
import {APP_INITIALIZER, NgModule} from '#angular/core';
import {HttpClientModule} from '#angular/common/http';
import {AppConfigService} from './app-config.service';
import {CometdService} from './cometd/cometd.service';
const appInitializerFn = (appConfig: AppConfigService) => {
return () => {
return appConfig.loadAppConfig();
}
};
#NgModule({
...
providers: [HttpClientModule,
AppConfigService,
{
provide: APP_INITIALIZER,
useFactory: appInitializerFn,
multi: true,
deps: [AppConfigService]
}]
})
export class AppModule {
constructor(cometdService: CometdService) {}
}
cometd.service.ts (/src/app/cometd/):
import {Injectable, OnDestroy} from '#angular/core';
import {Store} from '#ngrx/store';
import * as fromRoot from '../reducers';
import {AppConfigService} from '../app-config.service';
export interface CometDExtended extends cometlib.CometD {
websocketEnabled: boolean;
}
#Injectable({
providedIn: 'root'
})
export class CometdService implements OnDestroy {
protected cometd: CometDExtended = new cometlib.CometD() as CometDExtended;
private subscriptions: cometlib.SubscriptionHandle[] = [];
constructor(private environment: AppConfigService, private store: Store<fromRoot.State>) {
let config = environment.config;
let apiUrl = environment.config.apiUrl;
this.cometd.configure('http://localhost:8080/cometd');
this.startConnection();
}
...
}
The issue happens for various services. CometD is only an example.
The data in app-config.service.ts itself is fetched properly, i.e. loadAppConfig() returns { "apiUrl": "localhost:8080" }.
Injected environment (AppConfigService) is defined, i.e. it's of type Object.
environment.config is undefined, so environment.config.apiUrl returns an error: "TypeError: Cannot read properties of undefined (reading 'apiUrl')".

AppConfigService is not needed in providers array because providedIn: 'root' already make it available.
If you provide the service in different ways you may have multiple instances : one will be loaded, others would not.
If it still does not work, put a breakpoint to check if other services are created before the init completion. I recommand to move the calls out of the CometdService constructor, so you can perform the async call in a clean way

Welp, just minutes after posting the question I happened to find a solution. It seems like since loadAppConfig() is asynchronous, the environment.config might've been accessed before the promise was resolved. Changing the constructor to:
this.environment.loadAppConfig().then(() => {
let config = environment.config
...
});
fixed the issue.

Related

Cant understand the error: TypeError: Cannot read property 'subscribe' of undefined

I have written an implementation to obtain data from API calls. However, while testing the functionality I am getting the following errors even before writing any meaningful test cases:
TypeError: Cannot read property 'subscribe' of undefined
at DataComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/dashboard/job/job.component.ts:48:10)
at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:3405:1)
at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:3375:1)
at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:3327:1)
at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:8573:1)
at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:8672:1)
at tickRootContext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9885:1)
at detectChangesInRootView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9910:1)
at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10320:1)
at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/core/__ivy_ngcc__/fesm2015/testing.js:243:1)
I'm not sure what I'm missing, any help would be appreciated.
I looked into the following to get some understanding:
Why am I getting a "Failed: Cannot read property 'subscribe' of undefined" while running tests? -> the solution did not work in my case
karma TypeError "Cannot read property 'subscribe' of undefined"
and some other references.
Here are my code files. I have removed certain parts of my code that I felt were not relevant to the issue at hand.
dataService.ts code:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
import { IData } from '../model/data.model';
import { Observable, throwError } from 'rxjs';
import {catchError, tap} from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) { }
public getData(): Observable<IData[]>{
return this.http.get<IData[]>('url')
.pipe(
tap(data => console.log('Data Received')),
catchError(this.handleError)
);
}
private handleError(err: HttpErrorResponse){
//handle error code
}
}
component I am testing -> data.component.ts code:
import { Component, OnInit, Input} from '#angular/core';
import { IData } from '../../model/data.model';
import {DataService} from '../../service/data.service';
#Component({
selector: 'app-job',
templateUrl: './job.component.html',
styleUrls: ['./job.component.css']
})
export class JobComponent implements OnInit {
#Input() appId: string;
jobs: IData[] = [];
constructor( private dataService: DataService ) {}
ngOnInit(): void {
this.dataService.getData().subscribe({
next: data => {
this.data = data;
},
error: err => this.errorMessage = err
});
}
//other implementation
}
Testing file : data.spec.ts:
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { DataComponent } from './job.component';
import { IData } from 'src/app/model/job.model';
import { DataService } from 'src/app/service/job-data.service';
import { of, Observable } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '#angular/core';
describe('DataComponent', () => {
let component: DataComponent;
let fixture: ComponentFixture<DataComponent>;
let mockDataService ;
let JOBS: IData[];
beforeEach(async () => {
mockJobDataService = jasmine.createSpyObj(['getData']);
await TestBed.configureTestingModule({
declarations: [ DataComponent ],
providers: [
{provide: DataService, useValue: mockDataService}
]
})
.compileComponents()
.then(() => {
DATA = [{id: 'CJH'}];
fixture = TestBed.createComponent(DataComponent);
component = fixture.componentInstance;
fixture.detectChanges(); //updates bindings
});
});
it('should return true',() => {
expect(true).toBe(true);
})
});
When I remove fixture.detectChanges(), the error gets removed. But in my understanding the test cases should work even if I use this call anywhere in my test.
You are creating a spy object with mockJobDataService = jasmine.createSpyObj(['getData']); and are registering it correctly with this {provide: DataService, useValue: mockDataService}. At this point, your component should get created correctly with your fake service injected into it. The problem is that you aren't setting up the expected method call on your fake service.
Something like the following should do the trick as long as you do it before you call fixture.detectChanges(). Since you don't include the shape of the IJob interface, I can't tell you exactly, but I have made it work with typescript through casting it(({} as IJob)).
import { of } from 'rxjs';
mockJobDataService.getData.and.returnValue(of([({} as IJob)]));
This tells jasmine that anything using this fake data service, upon calling the getData method, the return from that method should be of([({} as IJob)])(this is an observable of type IJob array).
As you move forward and write tests that actually are testing your component, you will probably want to move the mocked method and the 'detectChanges' call into each test so that you can provide different fake data for every test.

Angular 6 Universal service provided in Injector needs another app injected variable

I am using Angular Universal. I have created a PlatformService to detect which platform I am currently working on.
/* platform.service.js */
import { Injectable, Inject, PLATFORM_ID } from '#angular/core';
import { isPlatformBrowser, isPlatformServer } from '#angular/common';
#Injectable({
providedIn: 'root'
})
export class PlatformService {
constructor(
#Inject(PLATFORM_ID) private platformId: Object
) {
this.platformId; // this is coming out undefined
}
isBrowser() {
return isPlatformBrowser(this.platformId);
}
isServer() {
return isPlatformServer(this.platformId);
}
}
I am creating a BaseComponent for common handling of my route binded components.
/* base.component.ts */
import { Component, OnInit, Inject } from '#angular/core';
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#Component({
selector: 'app-base',
template: '',
})
export class BaseComponent implements OnInit {
protected platformService: PlatformService;
constructor() {
this.platformService = InjectorHolderService.injector.get(PlatformService);
console.log(this.platformService);
}
}
Since this component will be inherited by many components, I didn't want to pass the PlatformService through super(). So I decided to go with creating an Injector.
/* app.module.ts */
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#NgModule({ ... })
export class AppModule {
constructor() {
InjectorHolderService.injector = Injector.create({
providers: [
{
provide: PlatformService,
useClass: PlatformService,
deps: [], // I think i need to put something here, but not sure.
}
]
});
}
}
And a service which can hold all the injected module for future use.
/* injector-holder.service.ts */
import { Injectable, Injector } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class InjectorHolderService {
static injector: Injector;
}
But #Inject(PLATFORM_ID) private platformId: Object is giving out undefined, because of which I am not able to detect the platform.
What am I missing here? or If there is a better approach to achieve the above functionality.
Please let me know if you guys need to see any other file.
I am not sure whether the following approach is good or bad, currently, it is the only thing working for me. Would love to hear any new approach to it.
Since PlatformService needed #Inject(PLATFORM_ID) which is provided only from AppModule, the new Injector I created was not able to find any value for #Inject(PLATFORM_ID) and hence undefined.
So, instead of using class PlatformService in Injector, now I am using PlatformService's instantiated object and hence was able to access everything fine in BaseComponent.
Modified my AppModule like following:
/* app.module.ts */
import { InjectorHolderService } from '#core/services/util/injector-holder.service';
import { PlatformService } from '#core/services/util/platform.service';
#NgModule({ ... })
export class AppModule {
constructor(
private platformService: PlatformService,
) {
InjectorHolderService.injector = Injector.create({
providers: [
{
provide: PlatformService,
useValue: this.platformService, // notice the change of key, using value not class
deps: [],
}
]
});
}
}
Will try to add a minimal repo to recreate this issue and share with you guys, If anyone wants to explore more.

How do I get data to display in Angular from API in Express?

I am trying to use Nodejs/Express as my back end for producing data from a database. I currently have an api route setup so that a database query will result in its directory. So if I visit localhost:3000/api currently I will see the following:
{"status":200,"data":[{"Issuer_Id":1,"Data_Id":2,"Data_Name":"Name 1"},{"Issuer_Id":2,"Data_Id":14,"Data_Name":"Name 2"},{"Issuer_Id":2,"Data_Id":1,"Data_Name":"Name 3"}],"message":null}
This leads me to believe I have everything setup correctly on the back end.
Now how do I get this data to display on my Angular front end?
I have been through hours of tutorials and this is what I have come up with:
nav.component.ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../../data.service';
import { Series } from '../../data.service';
import {Observable} from 'rxjs/Rx';
#Component({
selector: 'app-fixed-nav',
templateUrl: './fixed-nav.component.html',
styleUrls: ['./fixed-nav.component.css']
})
export class FixedNavComponent implements OnInit{
serieses: Series[] ;
constructor(private dataService: DataService) {}
ngOnInit() {
this.dataService.getSeries().subscribe((serieses: Series[]) => this.serieses = serieses);
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from'#angular/common/http';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/toPromise';
export class Series {
Issuer_Id: number;
Data_Id: number;
Data_Name: string;
}
#Injectable()
export class DataService {
constructor(private _http: Http) {}
getSeries(): Observable<Series[]> {
return this._http.get("http://localhost:3000/api/")
.map((res: Response) => res.json());
}
}
app.module.ts
import { Form1Module } from './modules/form1/form1.module';
import { FixedNavModule } from './modules/fixed-nav/fixed-nav.module';
import { HeaderModule } from './modules/header/header.module';
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { AppComponent } from './app.component';
import { HttpClientModule } from '#angular/common/http';
import { HttpModule } from '#angular/http';
import { DataService } from './data.service';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpModule,
HttpClientModule,
HeaderModule,
FixedNavModule,
Form1Module,
NgbModule.forRoot()
],
providers: [DataService],
bootstrap: [AppComponent]
})
export class AppModule { }
What do I need to enter in the nav.component.html to see the results?
Also note that when I refresh my angular page on lcoalhost:4200 I can see that the GET request is hitting the /apiu/ on the 3000 express server.
I am trying to help with best practices which might help get the intended result. I will amend this answer as we troubleshoot and hopefully arrive at the right answer.
So in your dataServices service I wanted to point out a couple things. Angular recommends we use the httpClient and not http and warn that http will soon be depreciated. I am fairly new to angular myself and have only ever used httpClient and have gotten great results so I recommend using that. I think this means that the promise that you are returned is changed too. Namely, you pust use a .pipe method inorder to use rxjs operators like map on the result. So this is what your dataService file would look like:
import { Injectable } from '#angular/core';
import { HttpClient } from'#angular/common/http';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/toPromise';
export class Series {
Issuer_Id: number;
Data_Id: number;
Data_Name: string;
}
#Injectable()
export class DataService {
constructor(private _http: HttpClient) {}
getSeries(): Observable<Series[]> {
return this._http.get<Series[]>("http://localhost:3000/api/")
.pipe(
map((res) => {
console.log(res);
return <Series[]> res
})
)
}
}
Note that I have imported map in a different rxjs/operators.
In actuality you dont even need to pipe or map the return since you have already declared the type of return in the get method of _http. HttpClient will cast the return into a Series[] for you so this one liner: return this._http.get("http://localhost:3000/api/") would work. I've written the code how it is however to console.log the return that your getting.
In the comments, could you tell me what is logged?
I am unable to correct your code I am providing my own setup Works for Me
In server.js
module.exports.SimpleMessage = 'Hello world';
Now in App.js
var backend = require('./server.js');
console.log(backend.SimpleMessage);
var data = backend.simpleMessage
In index html include App.js
<script src = '/App.js'></script>
alert(simpleMessage)
And you should get 'hello world'

Angular Server-Side Rendering with Route Resolve

I am attempting to use Server-Side Rendering in Angular (v4) to allow for better SEO.
Things work as expected until I add resolve on my route. Adding resolve causes HTML title to retain it's initial value when viewing source.
My Module:
import {
Injectable,
ModuleWithProviders,
NgModule
} from '#angular/core';
import {
ActivatedRouteSnapshot,
Resolve,
Router,
RouterModule,
RouterStateSnapshot
} from '#angular/router';
import {
Observable
} from 'rxjs/Rx';
import {
ArticleComponent
} from './article.component';
import {
Article,
ArticlesService,
UserService,
SharedModule
} from '../shared';
#Injectable()
export class ArticleResolver implements Resolve < Article > {
constructor(
private articlesService: ArticlesService,
private router: Router,
private userService: UserService
) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): any {
return this.articlesService.get(route.params['slug'])
.catch((err) => this.router.navigateByUrl('/'));
}
}
const articleRouting: ModuleWithProviders = RouterModule.forChild([{
path: 'article/:slug',
component: ArticleComponent,
resolve: {
article: ArticleResolver
},
data: {
preload: true
}
}]);
#NgModule({
imports: [
articleRouting,
SharedModule
],
declarations: [
ArticleComponent
],
providers: [
ArticleResolver
]
}) export class ArticleModule {}
My Component:
import {
Component,
OnInit
} from '#angular/core';
import {
ActivatedRoute,
Router,
} from '#angular/router';
import {
Title,
Meta
} from '#angular/platform-browser';
import {
AppComponent
} from '../app.component';
import {
Article,
} from '../shared';
#Component({
selector: 'article-page',
templateUrl: './article.component.html'
})
export class ArticleComponent implements OnInit {
article: Article;
constructor(
private route: ActivatedRoute,
private meta: Meta,
private title: Title
) {}
ngOnInit() {
this.route.data.subscribe(
(data: {
article: Article
}) => {
this.article = data.article;
}
);
this.title.setTitle(this.article.title);
}
}
I am new to Angular SSR so any guidance is greatly appreciated.
Instead of subscribing to route data, retrieve your results from the snapshot like this:
this.route.snapshot.data['article']
You also might need to register ArticlesService in your providers for the module.
As a side note, this import:
import {
Observable
} from 'rxjs/Rx';
is an RxJS antipattern. Please use the following import instead:
import {Observable} from 'rxjs/Observable';
I found that my primary service was referencing a secondary service that was attempting to return an authentication token from window.localStorage.
Attempting to access the client storage caused Angular SSR to omit the generation of source code for my component.
Thanks #Adam_P for helping me walk through it!

How to access module info inside another module in TypeScript?

I'm working on a NativeScript app by using AngularJS2 & TypeScript. I've created one file config.ts for storing my API URL.
code for config.ts
export class Config {
apiUrl = "https://incandescent-fire-8397.firebaseio.com/";
}
Then from an another file called user.service.ts I'm trying to access apiUrl value. But, don't know how to do it. Tried couple of permutation & combination but till now no luck.
Code for user.service.ts
import {Injectable} from "#angular/core";
import {User} from "./user";
import Config = require("../config");
#Injectable()
export class UserService {
config: any;
constructor() {
this.config = new Config();
}
register(user: User) {
alert("API url going to use is : "+this.config.apiUrl);
}
}
Need some guidance.
Regards
------Issue Fixed-------
Updated the user.service.ts file
import {Injectable} from "#angular/core";
import {User} from "./user";
import {ConfigService} from "../config";
#Injectable()
export class UserService {
apiUrl = "https://incandescent-fire-8397.firebaseio.com/";
register(user: User,config:ConfigService) {
//alert("About to register: " + user.email);
alert("API url going to use is : "+config.apiUrl);
}
}
Then, the module(app.componets.ts) from where I'm accessing the register method updated like this -
import {Component} from "#angular/core";
import {User} from "./shared/user/user";
import {UserService} from "./shared/user/user.service";
import {ConfigService} from "./shared/config";
import {HTTP_PROVIDERS} from "#angular/http";
import firebase = require("nativescript-plugin-firebase");
#Component({
selector: "my-app",
providers: [UserService, HTTP_PROVIDERS,ConfigService],
templateUrl: "pages/login/login.html",
styleUrls: ["pages/login/login-common.css", "pages/login/login.css"]
})
export class AppComponent {
user: User;
isLoggingIn = true;
config:any;
constructor(private _userService: UserService, private _configSerice:ConfigService) {
this.user = new User();
this.config = new ConfigService();
}
submit() {
if (this.isLoggingIn) {
this.login();
} else {
this.signUp();
}
}
login() {
// TODO: Define
console.log('Clicked on Login button');
firebase.init(<any>{
persist: true // Allow disk persistence. Default false.
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
}
signUp() {
this._userService.register(this.user,this.config);
}
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
}
}
Don't know how much it's a right way to do. But, it's fixed the problem. Will appriciate a lot if someone know any better way to do this.
I thing you're initiating the Config module instead of the Config class inside it.
You can try adding the 'default' keyword to the "export class Config" line, or try initiating the config instance with "new Config.Config()".

Categories

Resources