I am new to Angular so please excuse maybe simple question. I wrote an app, that loads config json using HttpClient and APP_INITIALIZER, but now all my tests are failing with message: TypeError: Cannot read properties of undefined (reading 'theme'). Can you please point me the right direction?
app.component.spec.ts:
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
});
app-config.service.ts:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class AppConfigService {
static config: IAppConfig;
constructor(private http: HttpClient) {}
loadConfig() {
return new Promise<void>(async (resolve, reject) => {
try {
const get$ = this.http.get('assets/app.config.json');
const configFile = await firstValueFrom(get$);
AppConfigService.config = <IAppConfig>configFile;
resolve();
} catch (e) {
console.error('Error loading config file', e);
reject(e);
}
});
}
}
export interface IAppConfig {
theme: string;
}
app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppConfigService } from './app-config.service';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
// Function to load application config on application startup
export function initializeApp(appConfigService: AppConfigService) {
return (): Promise<any> => {
return appConfigService.loadConfig();
};
}
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [
AppConfigService,
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [AppConfigService],
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
app.component.ts
import { Component } from '@angular/core';
import { AppConfigService } from './app-config.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
theme = AppConfigService.config.theme;
}
CodePudding user response:
you have a few issues, first you should not use the class
for setting properties. The service should declare some properties, and inside this service, you could set this.config = someValue
.
Then, if you want to use the Service in a component, you have to inject it, basically with the constructor through dependency injection : https://angular.io/guide/dependency-injection
constructor(heroService: HeroService)
And then you would add the service - preferably with mocking - as a provider in your test file. You acheive that with Spy
from Jasmine like so :
https://codecraft.tv/courses/angular/unit-testing/mocks-and-spies/
Also, I would drop Promises for Observables that are much more powerful. Cheers !
CodePudding user response:
So I made some changes and this is what I came with. It is working and all test are okay as well. No need for APP_INITIALIZE as well. Do you see any problems now?
app.component.spec.ts:
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RouterTestingModule, HttpClientTestingModule],
declarations: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
});
app-config.service.ts:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class AppConfigService {
constructor(private http: HttpClient) {}
loadConfig() {
return this.http.get<IAppConfig>('assets/app.config.json');
}
}
export interface IAppConfig {
theme: string;
}
app.module.ts:
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
bootstrap: [AppComponent],
})
export class AppModule {}
app.component.ts:
import { Component } from '@angular/core';
import { AppConfigService, IAppConfig } from './app-config.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
config: IAppConfig = {
theme: 'blue'
};
constructor(appConfigService: AppConfigService) {
appConfigService.loadConfig().subscribe(data => this.config = data)
}
}