Trigger Module Lazy Load Manually Angular 7 - javascript

Official documentation has quite a lot of information about how to load angular modules lazily. [link here]
const routes: Routes = [
{
path: 'customers',
loadChildren: './customers/customers.module#CustomersModule'
},
{
path: 'orders',
loadChildren: './orders/orders.module#OrdersModule'
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
This basically makes the module load when user visits /customers or /orders routes.
However, I can't figure out how do I load a module when from another module.
In my application I have these modules:
auth
core
events
flash messages
One route of my auth module (profile page) has to use ngrx store from events module.
My code looks like this:
import { Observable } from 'rxjs';
import { Component, OnInit } from '#angular/core';
import { Store } from '#ngrx/store';
import { AppState } from '../../app.store';
import { IUser } from '../auth.api.service';
import { selectUser } from '../store/auth.selectors';
import { IEvent } from '../../events/events.api.service';
import { selectAllEvents, selectIsLoading } from '../../events/store/events.selectors';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
})
export class ProfileComponent implements OnInit {
isLoading$: Observable<boolean>;
events$: Observable<IEvent[]>;
user$: Observable<IUser>;
constructor(
private store: Store<AppState>,
) {
this.user$ = this.store.select(selectUser);
this.isLoading$ = this.store.select(selectIsLoading);
this.events$ = this.store.select(selectAllEvents);
}
ngOnInit() {
}
}
However, as you can expect this code does not work. Because ../../events is not loaded yet. How do I load the module manually? Something like:
constructor(
private store: Store<AppState>,
) {
this.user$ = this.store.select(selectUser);
this.loadModule('../../events/events.module.ts').then(() => {
this.isLoading$ = this.store.select(selectIsLoading);
this.events$ = this.store.select(selectAllEvents);
})
}

The Angular CLI bundler bundles up the modules based on two things:
1) If you have the modules set up for lazy loading (loadChildren), it will bundle the module up separately and provide it lazily.
2) HOWEVER, if there are any references to a lazy loaded module in any other module (by adding it to its imports array), it instead bundles the module with the referenced component.
So what should be happening is that if your events module is referenced from a component, it should be bundled with that component.
Do you have the module referenced in the imports array for the module containing the component that references it?
What error are you getting exactly?
BTW - I cover this in the "lazy loading" part of this talk: https://www.youtube.com/watch?v=LaIAHOSKHCQ&t=1120s

You need not worry about loading the ../../events. Since you have the import statement, the class/interface would be available in the module. If for some reason, you want to use features of other modules, you can add the module name in the imports array in the #NgModule declaration.

Related

Change Angular base href with ngx-translate

I have already implemented ngx-translate succesfully. Now, I want to change the base href of my Angular project, depending on the language I choose from my header menu.
Currently, my URL looks like this: "localhost:4200". Then, when you launch the project, it must show something like this: "localhost:4200/en" or like this: "localhost:4200/es", depending on the choosen language.
My index html has this:
<base href="/"/>
And my header component ts file has a function that changes the language using ngx-translate. As you can see, I tried to use 'replaceState' to show the choosen language in the URL, and it worked, but it disappears once I navigate to another route.
import { Component, OnInit } from '#angular/core';
//For translate language
import { TranslateService } from '#ngx-translate/core';
import { Router, Event, NavigationStart, NavigationEnd } from '#angular/router';
import { Location } from '#angular/common';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
constructor(private translate: TranslateService,
private router: Router,
private location: Location,
)
{ translate.addLangs(['es','en']);
translate.setDefaultLang('es');
}
ngOnInit(): void {
}
useLanguage(language: string): void {
this.translate.use(language);
// alert(language);
// location.replace("https://www.google.com");
// return;
const modified_path = language;
this.location.replaceState(modified_path);
}
}
It looks like you are trying to achieve some kind of routing using base href. I would use base href only if I need multiple instances of my application. E.g. each of them in a subfolder.
Maybe you should give try on Angular Router (https://angular.io/guide/router-reference) if you want just one instance of the application handling different languages.
The idea is to have a route on the root level that represents the language and a language guard that ensures only valid languages are called.
This would look something like this:
const routes2: Routes = [
{
path: ':language',
canActivate: [LanguageGuard],
children: [
{
path: 'home',
component: HomeComponent,
},
{
path: 'some-page',
component: SomePageComponent,
},
]
},
{
path: '**',
redirectTo: '/en',
},
];

Routing Module loads before APP_INITIALIZER

I have a value that is from of a config file from static AppConfigService.
Described below:
reference code/article: https://blogs.msdn.microsoft.com/premier_developer/2018/03/01/angular-how-to-editable-config-files/
import { Injectable } from '#angular/core';
import { AppConfig } from './app-config';
import { HttpClient } from '#angular/common/http';
import { environment } from 'src/environments/environment';
#Injectable()
export class AppConfigService {
static settings: AppConfig;
constructor(private http: HttpClient) { }
load() {
console.log('is this getting fired before routing module check?');
const jsonFile = `assets/config/config.${environment.name}.json`;
return new Promise<void>((resolve, reject) => {
this.http.get(jsonFile)
.toPromise()
.then((response: AppConfig) => {
AppConfigService.settings = <AppConfig>response;
console.log(AppConfigService.settings);
resolve();
})
.catch((response: any) => {
reject(`Could not load file '${jsonFile}':
${JSON.stringify(response)}`);
});
});
}
}
This config gets loaded in my APP_INITIALIZER in the app.module.ts
providers: [
AppConfigService,
{
provide: APP_INITIALIZER,
useFactory: (appConfigService: AppConfigService) => () => {appConfigService.load() },
deps: [AppConfigService], multi: true
}
],
but my routing module, named AppRoutingModule is reading something out of my AppConfigService.settings variable which is crazy enough, UNDEFINED. My application crashes. I expect the APP_INITIALIZER to fire BEFORE AppRoutingModule but this is not the case:
Uncaught TypeError: Cannot read property 'oldUrl' of undefined
oldUrl is a property of AppConfigService.settings. I checked if AppConfigService.settings is set, it IS, properly AFTER routing module is fired but this is not what I want.
I checked some other sources for help. I used the following already as maybe a fix: https://github.com/angular/angular/issues/14615 and https://github.com/angular/angular/issues/14588
#component({})
class App {
constructor(router: Router, loginService: LoginService) {
loginService.initialize();
router.initialNavigation();
}
}
#NgModule({
imports: [
BrowserModule,
RouterModule.forRoot(routes, {initialNavigation: false})
],
declarations: [ App ],
bootstrap: [ App ],
providers: [ Guard, LoginService ]
})
export class AppModule {
}
Unfortunately, the above solution is not fixing my problem. I also tried to put in AppModule but alas, that didn't help either.
Any help is very welcome.
I've solved my App Initialization and Routing with NgRx listening the central state to know when the system is Loaded and activating the route Guards after that.
But for a direct solution, you need to add a Route Guard checking when your service is loaded. So, add a loaded: boolean flag in your Service, and check it from a Guard like this:
https://github.com/angular/angular/issues/14615#issuecomment-352993695
This is better handled with Observables tho, and I'm wiring all with NgRx in my Apps using Facades to facilitate everything:
https://gist.github.com/ThomasBurleson/38d067abad03b56f1c9caf28ff0f4ebd
Best regards.

Angular 5 service replacement/override

I created a core library for my project containing some components and services. I built the library with ng-packagr. In the consuming project which references the library I built my webapp containing components provided by the library. Nothing special so far. But sometimes I want a component (coming from my lib) calling a method from a Service outside of the lib. Is this possible? Can I somehow inject a service to a component which is defined inside a library?
Cheers
I've achieved this before with something like this:
Your library's service(s) should be defined as an interface rather than as a concrete implementation (as is done in OO languages quite often). If your implementing application will only sometimes want to pass in its own version of the service then you should create a Default service in your library, and use it as so:
import { Component, NgModule, ModuleWithProviders, Type, InjectionToken, Inject, Injectable } from '#angular/core';
export interface ILibService {
aFunction(): string;
}
export const LIB_SERVICE = new InjectionToken<ILibService>('LIB_SERVICE');
export interface MyLibConfig {
myService: Type<ILibService>;
}
#Injectable()
export class DefaultLibService implements ILibService {
aFunction() {
return 'default';
}
}
#Component({
// whatever
})
export class MyLibComponent {
constructor(#Inject(LIB_SERVICE) libService: ILibService) {
console.log(libService.aFunction());
}
}
#NgModule({
declarations: [MyLibComponent],
exports: [MyLibComponent]
})
export class LibModule {
static forRoot(config?: MyLibConfig): ModuleWithProviders {
return {
ngModule: LibModule,
providers: [
{ provide: LIB_SERVICE, useClass: config && config.myService || DefaultLibService }
]
};
}
}
Then in your implementing application you have the ability to pass in the optional config via your library's forRoot method (note that forRoot should only be called once per application and at the highest level possible). Note that I've marked the config parameter as optional, so you should call forRoot even if you have no config to pass.
import { NgModule, Injectable } from '#angular/core';
import { LibModule, ILibService } from 'my-lib';
#Injectable()
export class OverridingService implements ILibService {
aFunction() {
return 'overridden!';
}
}
#NgModule({
imports: [LibModule.forRoot({ myService: OverridingService })]
})
export class ImplementingModule {
}
This was from memory as I don't have the code to hand at the moment so if it doesn't work for any reason let me know.

Angular How to test a Component which requires a Location

Hello and thank you for your time!
I am learning how to use Angular and I am interested in learning how to test its Components.
Currently I am struggling because I have done the Tour of Heroes tutorial of the Angular page and I am testing the code to understand it better.
The point is that I am testing hero-details component which code is:
import {Component, OnInit, Input} from '#angular/core';
import {ActivatedRoute} from '#angular/router';
import {MyHeroService} from '../hero-service/my-hero.service';
import {Location} from '#angular/common';
import {Hero} from '../Hero';
#Component({
selector: 'app-hero-details',
templateUrl: './hero-details.component.html',
styleUrls: ['./hero-details.component.css']
})
export class HeroDetailsComponent implements OnInit {
#Input() hero: Hero;
constructor(private route: ActivatedRoute,
private myHeroService: MyHeroService,
private location: Location) {
}
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.myHeroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
}
And my test tries to prove that getHero() is called after creating the hero-details component:
import {HeroDetailsComponent} from './hero-details.component';
import {ActivatedRoute} from '#angular/router';
import {MyHeroService} from '../hero-service/my-hero.service';
import {MessageService} from '../message.service';
import {Location} from '#angular/common';
import {provideLocationStrategy} from '#angular/router/src/router_module';
import {BrowserPlatformLocation} from '#angular/platform-browser/src/browser/location/browser_platform_location';
describe('heroDetails', () => {
it('should call getHero after being created', () => {
const heroDetailsComponent = new HeroDetailsComponent(new ActivatedRoute(),
new MyHeroService(new MessageService([])),
new Location(provideLocationStrategy(new BrowserPlatformLocation(['anyParameter']), '/')));
spyOn(heroDetailsComponent, 'getHero');
heroDetailsComponent.ngOnInit();
expect(heroDetailsComponent.getHero()).toHaveBeenCalled();
});
});
The difficulty I am facing is when I try to create a new Location which is a required parameter for the Hero-datail component's constructor.
The first Location's parameter is a PlatformStrategy, so then I used the provider to build it. Also, the provider needs a PlatformLocation() which looks like is abstract so then I chose the only implementation I could find which is BrowserPlatformLocation.
After all that process, the test execution says:
And the browser never ends loading the suite:
The strange thing here is that the IDE indeed finds those modules because I can navigate to them.
Also if I comment out that test, the suite works well:
Additionaly I have also read:
https://angular.io/api/common/Location
https://www.tektutorialshub.com/location-strategies-angular/
https://angular.io/tutorial/toh-pt5
How could I test it in a correct way? Where could I find more information about doing this type of tests properly? How could do this test to mock easily that Location parameter?
Thank you for reading this
Location is a built-in service, you do not need to instantiate it, just mock it:
const locationStub = {
back: jasmine.createSpy('back')
}
Then in your providers array:
providers: [ ..., {provide: Location, useValue: locationStub} ],
Then in your test just call the components goBack method, then use the Injector to get the instance of your service stub, like this:
const location = fixture.debugElement.injector.get(Location);
And then just test, that the back function has been called:
expect(location.back).toHaveBeenCalled();
This should solve the problem. This is, as far as I have seen, the best way to deal with the built-in services, you don't need to test them (Angular team did that already), just mock them and make sure they have been called correctly
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeroDetailsComponent ],
providers: [ MyHeroService ],
imports: [ RouterTestingModule ],
providers: [{ provide: Location, useClass: SpyLocation }]
})
.compileComponents();
}));
it('should logout from application', async(() => {
const location: Location = TestBed.get(Location);
expect(location.href).toContain('blablabla url');
}));
Use SpyLocation from #angular/router/testing
If I were you, I would not bother creating tests from scratch : the CLI creates pre-made tests.
In those tests, there's a TestBed, that is used to set up a testing module for testing your component. If you used it, you would only have to import a router testing module.
This would give something like this :
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeroDetailsComponent ],
providers: [ MyHeroService ],
imports: [ RouterTestingModule ]
})
.compileComponents();
}));
And just with that, your whole routing strategy is mocked.

How to route between modules in angular2?

I am using angular2 Final for development.
I have created 3 modules.
AppModule
ProjectModule
DesignerModule
Previously I had only AppModule,In Which I had imported following RoutingModule & it worked fine.
import {NgModule} from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import {ProjectManagerComponent} from './project-manager/project-manager.component';
import {DesignerComponent} from './designer/designer.component';
const appRoutes: Routes = [
{path: '',redirectTo: 'project-manager',pathMatch: 'full'},
{ path: 'project-manager', component: ProjectManagerComponent },
{ path: 'designer/:id', component:DesignerComponent }
];
#NgModule({
imports:[RouterModule.forRoot(appRoutes)],
exports:[RouterModule]
})
export class AppRoutingModule { }
export const routingComponents=[ProjectManagerComponent,DesignerComponent]
But recently I have created separate NgModules for ProjectManager & Designer.
I have kept ProjectManagerComponent,DesignerComponent inside declarations in their respective modules.
I want to know if it is possible to route to these modules using same routing configuration or do I need to change something.
My Routing is not working anymore.
any inputs?
instead of
export const routingComponents=[ProjectManagerComponent,DesignerComponent]
you can just
export const routing = RouterModule.forRoot(appRoutes);
Since you app.module.ts already knows about ProjectManagerComponent and DesignerComponent from their respective modules. All that is left is to teach it where to go and find them.
Inside of your app.module.ts you would have
// All the other imports
import {routing} from './app.routes;'
#NgModule({
imports:[routing, /*Other Modules*/],
})

Categories