I'm having an issue with my Angular 6 app that I'm breaking up into smaller modules. Originally everything was imported into AppModule, and it worked just fine, but it was a giant file and testing was overly complicated.
The issue I'm having is basically creating a shared-module for a few commonly used services across the app. ng build runs fine so it seems to build alright, but when I serve the app I get the error that "ConfigService.foo()" is not a function! I'm obviously doing something wrong to group shared services into a shared module.
Here's some code:
SharedModule
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '#angular/common/http';
import { Injectable, OnInit, NgModule } from '#angular/core';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import * as $ from 'jquery';
import { PipeModule } from './pipes/pipes.module';
import { ConfigService } from "./config.service";
#NgModule({
imports: [
PipeModule
],
declarations: [
],
exports: [
PipeModule
],
providers: [
ConfigService
]
})
export class SharedModule {
static forRoot() {
return {
ngModule: SharedModule,
providers: [
ConfigService, {
provide: ConfigService,
useValue: ConfigService
},
],
};
}
}
ConfigService
import {Injectable} from "#angular/core";
import {HttpClient, HttpHeaders, HttpParams, HttpResponse} from "#angular/common/http";
import {HttpObserve} from "#angular/common/http/src/client";
import {Observable, Subject, Observer, BehaviorSubject, throwError} from 'rxjs';
import {Config} from '../shared/config';
import { map, catchError} from 'rxjs/operators';
import * as _ from 'lodash';
#Injectable()
export class ConfigService {
constructor(private httpClient: HttpClient, private config: Config) {
this.config = new Config();
}
ngOnInit() { }
foo() {
console.log("Hack the planet!!! (confgurably)");
}
}
AppModule
import { BrowserModule } from '#angular/platform-browser';
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NgModule, PipeTransform} from '#angular/core';
import {HttpClient, HttpClientModule} from "#angular/common/http";
import {AppRoutingModule} from './app-routing.module';
import {FormsModule} from "#angular/forms";
import { AppComponent } from './app.component';
import { BannerComponent } from './banner/banner.component';
import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
import { SearchComponent } from './search/search.component';
import { SharedModule } from './shared/shared.module';
#NgModule({
declarations: [
AppComponent,
BannerComponent,
BreadcrumbComponent,
SearchComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
AppRoutingModule,
SharedModule.forRoot()
],
providers: [
HttpClient
],
bootstrap: [AppComponent]
})
export class AppModule { }
What am I doing wrong with my Shared-Module and injectable services? For reference, I'm using Angularv6.
I just figured it out.
It turns out, when providing your own services in a module, you don't use
{ provide: ConfigService, useValue: ConfigService },
Instead, you simply list the services under providers in your exported module, like this:
export class SharedModule {
static forRoot() {
return {
ngModule: SharedModule,
providers: [
ConfigService
],
};
}
}
Cheers!
Related
I am upgrading my angualr from 6 to 13.0.2.
When I do ng build , I get an error:
NG6002: Appears in the NgModule.imports of AppModule, but itself has errors 33 export class AdminModule { }
I am sure, Admin module imports doesn't include any components, all components are part of declarations: []
Not sure what the error is related to. I also upgarded angualr material to 13.0.2.
I tried to setenableIvy to false in tsconfig.app.json.
"angularCompilerOptions": {
"enableIvy" : false,
admin.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule, NO_ERRORS_SCHEMA } from '#angular/core';
import { CommonModule } from '#angular/common';
import {MatRadioModule} from '#angular/material/radio';
import { ReactiveFormsModule, FormsModule } from '#angular/forms';
import { NgxSpinnerModule } from 'ngx-spinner';
import { MatNativeDateModule } from '#angular/material/core';
import { MaterialModule } from 'src/app/material/material.module';
#NgModule({
imports: [
CommonModule,
MaterialModule,
MatRadioModule,
ReactiveFormsModule,
FormsModule,
NgxSpinnerModule,
MatNativeDateModule
],
declarations: [AdminComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AdminModule { }
app.module.ts
#NgModule({
declarations: [AppComponent, LoginComponent, HomeComponent],
imports: [
AdminModule,
NgxSpinnerModule,
MatDialog,
NgxSliderModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
,
providers: [
],
bootstrap: [AppComponent],
})
export class AppModule {}
I have several components (about,service,dashboard, etc) in which a header component is added. The application is running fine. However, I am getting errors when testing.
import { Component } from '#angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '#angular/core/testing';
import { IonicModule } from '#ionic/angular';
import { SettingsPage } from './settings.page';
describe('SettingsPage', () => {
let component: SettingsPage;
let fixture: ComponentFixture<SettingsPage>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ SettingsPage,MockHeaderComponent ],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(SettingsPage);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});
#Component({
selector: 'app-header',
template: ''
})
class MockHeaderComponent {
}
First Error
Chrome 93.0.4577.82 (Windows 10): Executed 22 of 26 (1 FAILED) (0 secs / 5.88 secs)
ERROR: 'NG0303: Can't bind to 'headline' since it isn't a known property of 'app-header'.'
headline is a variable that I declared in my header component and use in my components to give me the correct title.
Of course, I already looked for solutions on the Internet and came across the following Stack Overflow post: Unit test Angular with Jasmine and Karma, Error:Can't bind to 'xxx' since it isn't a known property of 'xxxxxx'.
Therefore, I have imported and declared my HeaderComponent.
import { Component } from '#angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '#angular/core/testing';
import { IonicModule } from '#ionic/angular';
import { HeaderComponent } from '../header/header.component';
import { SettingsPage } from './settings.page';
describe('SettingsPage', () => {
let component: SettingsPage;
let fixture: ComponentFixture<SettingsPage>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ SettingsPage,HeaderComponent,MockHeaderComponent ],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(SettingsPage);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});
#Component({
selector: 'app-header',
template: ''
})
class MockHeaderComponent {
}
Unfortunately, the following error occurred.
Chrome 93.0.4577.82 (Windows 10) SettingsPage should create FAILED
Failed: NG0300: Multiple components match node with tagname app-header. Find more at https://angular.io/errors/NG0300
error properties: Object({ code: '300' })
Error: NG0300: Multiple components match node with tagname app-header. Find more at https://angular.io/errors/NG0300
at throwMultipleComponentError (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:6779:1)
at findDirectiveDefMatches (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10344:1)
at resolveDirectives (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10158:1)
at elementStartFirstCreatePass (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:14772:1)
at ɵɵelementStart (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:14809:1)
at ɵɵelement (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:14888:1)
at SettingsPage_Template (ng:///SettingsPage.js:8:9)
at executeTemplate (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9600:1)
at renderView (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:9404:1)
at renderComponent (node_modules/#angular/core/__ivy_ngcc__/fesm2015/core.js:10684:1)
Error: Expected undefined to be truthy.
at <Jasmine>
at UserContext.<anonymous> (src/app/components/settings/settings.page.spec.ts:24:23)
at ZoneDelegate.invoke (node_modules/zone.js/dist/zone-evergreen.js:364:1)
at ProxyZoneSpec.push.QpwO.ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:292:1)
Chrome 93.0.4577.82 (Windows 10): Executed 26 of 26 (7 FAILED) (1.712 secs / 1.658 secs)
Can anyone tell me what I am doing wrong?
UPDATE 1:
My ComponentsModule class in which I integrate all reusable components:
import {NgModule} from '#angular/core';
import {HeaderComponent} from './header/header.component';
import { FooterComponent } from './footer/footer.component';
#NgModule({
declarations: [HeaderComponent, FooterComponent],
exports: [HeaderComponent, FooterComponent]
})
export class ComponentsModule{}
My html:
<ion-header>
<app-header [headline]="headlines.settings"></app-header>
</ion-header>
<ion-content>
</ion-content>
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { IonicModule } from '#ionic/angular';
import { SettingsPageRoutingModule } from './settings-routing.module';
import { SettingsPage } from './settings.page';
import { ComponentsModule } from '../components.module';
#NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ComponentsModule,
SettingsPageRoutingModule
],
declarations: [SettingsPage]
})
export class SettingsPageModule {}
setting.page.ts
import { Component, OnInit } from '#angular/core';
import { lablesHeadlines } from 'src/environments/lables';
#Component({
selector: 'app-settings',
templateUrl: './settings.page.html',
styleUrls: ['./settings.page.scss'],
})
export class SettingsPage implements OnInit {
lablesHeadlines = lablesHeadlines;
headlines = lablesHeadlines;
constructor() { }
ngOnInit() {
}
}
setting.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { IonicModule } from '#ionic/angular';
import { SettingsPageRoutingModule } from './settings-routing.module';
import { SettingsPage } from './settings.page';
import { ComponentsModule } from '../components.module';
#NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ComponentsModule,
SettingsPageRoutingModule
],
declarations: [SettingsPage]
})
export class SettingsPageModule {}
Problem Solved. https://testing-angular.com/testing-components-with-children/#testing-components-with-children showed me the solution. That I write unit tests, I can do with
schemas: [NO_ERRORS_SCHEMA] to ignore the components in the top-level components. The low-level components are ready checked in their own tests.
I strongly advise to use the link above if you want to learn about testing in Angular.
Here is my Updated Sourcecode:
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { ComponentFixture, TestBed, waitForAsync } from '#angular/core/testing';
import { IonicModule } from '#ionic/angular';
import { AboutClientServiceService } from 'src/app/services/about-client-service.service';
import { AppComponent } from 'src/app/app.component';
import { MockAboutClientService } from 'src/app/Mock/MockAboutClientService';
import { AboutPage } from './about.page';
import { Component } from '#angular/core';
import { NO_ERRORS_SCHEMA } from '#angular/core';
describe('AboutPage', () => {
let component: AboutPage;
let fixture: ComponentFixture<AboutPage>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ AboutPage, MockHeaderComponent ],
schemas: [NO_ERRORS_SCHEMA],
imports: [IonicModule, HttpClientTestingModule],
providers: [AppComponent, AboutClientServiceService]
}).compileComponents();
TestBed.overrideComponent(
AboutPage,
{
set: {
providers: [
{ provide: AboutClientServiceService, useClass: MockAboutClientService }]
}
});
fixture = TestBed.createComponent(AboutPage);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', (done) => {
expect(component).toBeTruthy();
expect(component.version).toBeDefined();
done();
});
});
#Component({
selector: 'app-header',
template: ''
})
class MockHeaderComponent {
}
I am using angular in my ionic and I have followed everything based on this docs, so im using "#ionic/storage-angular", but my problem is when i refresh, data get deleted. Below is my code.
//app.module
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { RouteReuseStrategy } from '#angular/router';
import { IonicModule, IonicRouteStrategy } from '#ionic/angular';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { HttpClientModule } from '#angular/common/http';
import { IonicStorageModule } from '#ionic/storage-angular';
#NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
IonicStorageModule.forRoot(),
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule
],
providers: [
{
provide: RouteReuseStrategy,
useClass: IonicRouteStrategy,
},
],
bootstrap: [AppComponent]
})
export class AppModule {}
//storage.service
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage-angular';
#Injectable({
providedIn: 'root'
})
export class StorageService {
private _storage: Storage | null = null;
constructor(private storage: Storage){
this.init();
}
async init() {
// https://github.com/ionic-team/ionic-storage
// If using, define drivers here: await this.storage.defineDriver(/*...*/);
const storage = await this.storage.create();
this._storage = storage;
}
public set(key: string, value: any) {
this._storage?.set(key, value);
}
public get(key){
return this._storage?.get(key);
}
public clear(){
this._storage.clear();
}
}
//this is how i used the service
await this.storageService.set('loginUser',{id: user.id,userName: user.first_name});
I'm trying to retrieve a request param from a deeplink to a Ionic 5 application using Deeplink plugin (authorization code provided by authorization server on successful authorization redirection).
Android intent filter seems correctly configured as the app is opening after successful authentication.
I keep having unmatched deeplinks with plugin_not_installed error.
app.module.ts:
import { HttpClientModule } from '#angular/common/http';
import { APP_INITIALIZER, NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { RouteReuseStrategy } from '#angular/router';
import { Deeplinks } from '#ionic-native/deeplinks/ngx';
import { SQLitePorter } from '#ionic-native/sqlite-porter/ngx';
import { SQLite } from '#ionic-native/sqlite/ngx';
import { Vibration } from '#ionic-native/vibration/ngx';
import { IonicModule, IonicRouteStrategy } from '#ionic/angular';
import { IonicStorageModule } from '#ionic/storage';
import { ApiModule as NumeraApiModule } from '#lexi-clients/numera';
import { OidcUaaModule } from '#lexi/oidc-uaa';
import { AuthModule, OidcConfigService } from 'angular-auth-oidc-client';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SettingsService } from './settings/settings.service';
export function loadSettings(config: SettingsService) {
return () => config.load();
}
export function configureAuth(oidcConfigService: OidcConfigService) {
return () => oidcConfigService.withConfig(environment.authentication.angularAuthOidcClient);
}
#NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
AuthModule.forRoot(),
BrowserModule,
HttpClientModule,
IonicModule.forRoot(),
IonicStorageModule.forRoot(),
NumeraApiModule,
OidcUaaModule,
AppRoutingModule,
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: loadSettings,
deps: [SettingsService],
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: configureAuth,
deps: [OidcConfigService],
multi: true,
},
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
Deeplinks,
OidcConfigService,
SQLite,
SQLitePorter,
Vibration,
],
bootstrap: [AppComponent],
})
export class AppModule {}
app.component.ts:
import { AfterViewInit, Component, NgZone, OnDestroy, OnInit } from '#angular/core';
import { NavigationEnd, Router } from '#angular/router';
import { App, Plugins, StatusBarStyle } from '#capacitor/core';
import { AppCenterCrashes } from '#ionic-native/app-center-crashes';
import { Deeplinks } from '#ionic-native/deeplinks/ngx';
import { NavController, Platform } from '#ionic/angular';
import { LexiUser, UaaService } from '#lexi/oidc-uaa';
import { Observable, Subscription } from 'rxjs';
import { SettingsPage } from './settings/settings.page';
import { SettingsService } from './settings/settings.service';
#Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent implements AfterViewInit, OnInit, OnDestroy {
constructor(
private platform: Platform,
private router: Router,
private idService: UaaService,
private settings: SettingsService,
private navController: NavController,
private deeplinks: Deeplinks,
private zone: NgZone
) {
this.platform.ready().then(async () => {
if (this.platform.is('mobile')) {
const { SplashScreen, StatusBar } = Plugins;
StatusBar.setStyle({ style: StatusBarStyle.Light });
SplashScreen.hide();
}
});
}
ngAfterViewInit() {
if (this.platform.is('mobile')) {
this.deeplinks.routeWithNavController(this.navController, { 'login-callback': SettingsPage }).subscribe(
(match) => {
console.log('Successfully matched route', match);
// Create our internal Router path by hand
const internalPath = '/settings';
// Run the navigation in the Angular zone
this.zone.run(() => {
this.router.navigateByUrl(internalPath);
});
},
(nomatch) => {
// nomatch.$link - the full link data
console.error("Got a deeplink that didn't match", nomatch);
}
);
}
}
}
I got it. My Ionic project is a module in an Angular multi-module project and plugin npm dependencies where added to root package.json.
Moving all Ionic-native related dependencies to package.json in Ionic project folder and running ionic cap sync again solved my problem.
Now the question is How to properly configure a Ionic module in an Angular mono-repo (aka multi-module project)?
Without deactivating the old forms with (in RC4 > it was a part of bootstrapping process)
disableDeprecatedForms(),
provideForms()
I cannot render data (got over observables) e.g. in a select boxes (options/*ngFor).
Where can I deactivate the old forms in RC5?
(OverviewComponent below has the form with select boxes)
That's the new main.ts:
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
That's the new app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { APP_BASE_HREF } from '#angular/common';
import { RouterModule } from '#angular/router';
import { HttpModule } from '#angular/http';
import { FormsModule } from '#angular/forms';
import { AppComponent } from './app.component';
import { routes } from './app.routes';
import {
OverviewComponent,
} from './forms/index';
#NgModule({
imports: [
BrowserModule,
HttpModule,
FormsModule,
RouterModule.forRoot(routes),
],
declarations: [
AppComponent,
OverviewComponent,
BasisdateneingabeComponent
],
providers: [{
provide: APP_BASE_HREF,
useValue: '<%= APP_BASE %>'
}],
bootstrap: [AppComponent]
})
export class AppModule { }
According to this, disableDeprecatedForms() still exists in RC5 but has been deprecated. Your problems are probably coming from the fact that your application is using the RC4 forms API. The best solution would be to update your forms to RC5 avoid further problems with new releases.
In lieu of that, you should be able to find a fix here (Bootstrapping has changed a little bit since that was written). There is a DeprecatedFormsModule that you can use. In your app.module, import it and add it to the imports array. Like so:
//...
import {DeprecatedFormsModule} from '#angular/common';
#NgModule({
//...
imports: [
//....
DeprecatedFormsModule
],
//...
})