angular 9 - module lazy loading in production mode not working - lazy-loading

I have a library project in which I have a single module along with few components. In my actual application I want to load the library module dynamically and render the components. Everything working fine in development mode, but when I run my application with production mode always getting empty array of components from factory (with moduleFactory.componentFactories). Below is my code. Can anyone help me on this please. I am using Angular 9.1.0.
import { Injectable, Type, Compiler, Injector, ComponentFactory, NgModuleFactory } from '#angular/core';
import { Observable, of } from 'rxjs';
import { delay, tap } from 'rxjs/operators';
import { ApptorCustomCompHostComponent } from '#apptor/corecomps';
#Injectable({
providedIn: 'root',
})
export class LazyLoaderService {
private modules: Map<string, any> = new Map();
private componentRegistry: any[] = [
{ type: "textbox", className: "ApptorCorecompsWrapperComponent", moduleName: "ApptorCorecompsModule" }
];
constructor(private compiler: Compiler, private injector: Injector) {
}
public async getComponentFactory(componentType: string): Promise<ComponentFactory<any>> {
let compInfo = this.componentRegistry.find(c => c.type === componentType);
if (compInfo) {
let factories = this.modules.get(compInfo.moduleName);
if (!factories) {
await this.loadModule(compInfo.moduleName);
factories = this.modules.get(compInfo.moduleName);
}
let componentFactory = factories.find(f => compInfo.className === f.componentType.name);
return componentFactory;
}
return null;
}
async loadModule(moduleName: string) {
switch (moduleName) {
case 'ApptorCorecompsModule':
this.loadModuleInternal(moduleName, await import('#apptor/corecomps').then(m => m[moduleName]));
break;
case 'lazy':
this.loadModuleInternal(moduleName, await import('#apptor/corecomps').then(m => m[moduleName]));
break;
}
}
private loadModuleInternal(moduleName: string, moduleType: Type<any>) {
const moduleFactory = this.compiler.compileModuleAndAllComponentsSync(moduleType);
const componentFactories = moduleFactory.componentFactories;
this.modules.set(moduleName, componentFactories);
console.log("module loaded is: ", moduleName);
}
}

We made it working as below. Hope this may help others.
import { Injectable, Compiler, Injector, ComponentFactory, ComponentFactoryResolver } from '#angular/core';
import { ReplaySubject } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class LazyLoaderService {
private modules: Map<string, ReplaySubject<any>> = new Map();
private componentRegistry: any[] = [
{ type: "textbox", className: "ApptorCorecompsWrapperComponent", moduleName: "ApptorCorecompsModule" }
];
constructor(private compiler: Compiler, private injector: Injector,
private factoryResolver: ComponentFactoryResolver) {
}
public async getComponentFactory(componentType: string): Promise<ComponentFactory<any>> {
//this.loadComponent();
const compInfo = this.componentRegistry.find(c => c.type === componentType);
if (!compInfo)
throw new Error("Invalid component type:" + componentType);
let modSubject = this.modules.get(compInfo.moduleName);
if (!modSubject) {
modSubject = this.loadModule(compInfo.moduleName);
}
let ret: Promise<ComponentFactory<any>> = new Promise((resolve, reject) => {
modSubject.subscribe(async (moduleRef) => {
//await this.compileModule(mod);
const compType = moduleRef.instance.controls[compInfo.className];
if (compType) {
let componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(compType);
// let componentFactory = this.factoryResolver.resolveComponentFactory(compType);
resolve(componentFactory);
} else {
console.error("Component :" + compInfo.className + " not found");
reject(null);
}
});
});
return ret;
}
public loadModule(moduleName): ReplaySubject<any> {
//let mod:any = null;
let mod: ReplaySubject<any> = new ReplaySubject();
switch (moduleName) {
case 'ApptorCorecompsModule':
import('#apptor/corecomps').then(async m => {
let mm = m[moduleName]
mm = await this.compileModule(mm);
mod.next(mm);
//return mm;
});
break;
}
if (mod) {
this.modules.set(moduleName, mod);
return mod;
}
else {
console.error("Failed to load module: " + moduleName);
throw new Error("Failed to load module: " + moduleName);
}
}
private async compileModule(module: any) {
let ret: Promise<any> = new Promise((resolve, reject) => {
this.compiler.compileModuleAsync(module).then(factory => {
const moduleRef = factory.create(this.injector);
resolve(moduleRef);
});
});
return ret;
}
}

Related

is there any solution to get data from different api at once

import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class PokemonDataService {
pokeData = 'https://pokeapi.co/api/v2/pokemon/'
pokeUrl = 'https://pokeapi.co/api/v2/pokemon?limit=151'
constructor(private http:HttpClient) { }
getPoke(){
return this.http.get<any>(this.pokeUrl)
}
getData(){
for(let i=1;i<152;i++){
return this.http.get<any>(this.pokeData+i)
}
}
}
I want to get data from api https://pokeapi.co/api/v2/pokemon/1, https://pokeapi.co/api/v2/pokemon/2 like this
Yeah, you can do this:
getData(): Observable<any[]>{
const observables: Array<Observable<any>> = [];
for(let i=1;i<152;i++){
const obs: Observable<any> = this.http.get<any>(this.pokeData+i)
.pipe(catchError(err => of({})));
observables.push(obs);
}
// returns when all the observables emit
return forkJoin(observables);
}
and you can use like this:
getData().subscribe((pokemons: any[]) => console.log(pokemons.length));

How to fix the navigation

I’m setting up a new service for navigation to "Chat" service but it doesn't do anything and I don't know why.
This is for a new component
here is the "onCancel" button that use the "getCustomerService" function..
handleUnrecognizedUser = () => {
const infoMsg = {
onCancel: getCustomerService
};
};
here is the "getCustomerService" function that get called
import { AppStore, RoutingStore } from '../../stores'
import call from 'react-native-phone-call'
callServeiceCenter = (number) => {
const args = {
number, // String value with the number to call
prompt: false // Optional boolean property. Determines if the user should be prompt prior to the call
}
return call(args).catch(console.error)
}
export default getCustomerService = () => {
if (AppStore.isWorkingHours)
RoutingStore.goTo('Chat')
else {
callServeiceCenter(AppStore.getCallCenterPhone)
}
}
this is for the "RoutingStore" :
import { observable, action, computed } from "mobx";
import { NavigationActions, StackActions, DrawerActions } from 'react-navigation'
class RoutingStore {
#observable nav = null;
#observable PrevPage = null;
#observable curentPage = null;
#observable isGoBackAvailable = true;
#observable isLoggedIn = false;
#action
setNavigation(data) {
this.nav = data
}
goTo = (data, _params) => {
let { routeName, params } = data
const navigateAction = NavigationActions.navigate(
routeName
? { routeName, params }
: { routeName: data, params: { ..._params } })
this.nav.dispatch(navigateAction)
}
#action
goBack = () => {
}
#action
updateCurrentPage(data) {
this.curentPage = data
}
#action
updatePrevPage(data) {
this.PrevPage = data
}
updatePages = (prev, cur) => {
this.updatePrevPage(prev)
this.updateCurrentPage(cur)
}
#action
setLoggedIn(status) {
this.isLoggedIn = status
}
#action
openDrawer() {
this.nav.dispatch(DrawerActions.openDrawer())
}
#action
closeDrawer() {
this.nav.dispatch(DrawerActions.closeDrawer())
}
#action
toggleDrawer() {
this.nav.dispatch(DrawerActions.toggleDrawer())
}
disableLoginRoute = (route) => {
const resetAction = StackActions.reset({
index: 0,
key: null,
actions: [NavigationActions.navigate({ routeName: route })],
});
this.nav.dispatch(resetAction)
}
isGoBackAllowed = () => {
switch (this.curentPage) {
case "Tabs": return false
case "Login": return false
default: return this.goBack()
}
}
#computed
get isNonLogin() {
return this.isLoggedIn;
}
#computed
get getCurentPage() {
return this.curentPage;
}
}
const routingStore = new RoutingStore();
export default routingStore;
I expect to navigate to the Chat as well.

Testing NgRx 6 Effects

I am trying to test a ngrx effects in Angular 6 project, I always get error:
Expected $[0].notification.kind = 'C' to equal 'N'.
Expected $[0].notification.hasValue = false to equal true.
I tried this post https://brianflove.com/2018-06-28/ngrx-testing-effects and the one in the ngrx doc. Is there any requirements to make test on effects with ngrx 6 ? The error is not meaningful enough for me. Maybe someone have a complete example about how to do ?
Here's my effect:
initData$: Observable<Action> = this.actions$.pipe(
ofType(INIT_DATA_ACTION),
switchMap((data: any) => {
return this.store.pipe(select(getAppDataResolved)).take(1).switchMap((resolved: any) => {
if (!resolved) {
return this.dataService.getInitData(this.loginService.user.id).switchMap((response: any) => {
return Observable.from([
new ItemsInitDataAction(response.userItems),
new InitDataResolvedAction(),
]);
});
} else {
return Observable.from([
new InitDataResolvedAction(),
]);
}
});
}),
);
and my karma test:
it('should be created', () => {
expect(effects).toBeTruthy(); // got success
});
it('basic test', () => { // got error
const action = new appAction.InitDataAction();
const outcome = new appAction.InitDataResolvedAction();
actions.stream = hot('a', { a: action });
const expected = hot('a', { b: outcome });
expect(effects.initData$).toBeObservable(expected);
});
});
Thanks in advance for helping ;-)
I think you need to insert a mock for the Selector, the next line in the console should be describe something about missing selector data.
let store: Store<any>
class MockStore {
select(){}
}
TestBed.configureTestingModule({
providers: [
{
provide: Store,
useClass: MockStore
}
]
});
store = TestBed.get(Store);
And in test suite you can use Spy to give you any slice of store that you want:
spyOn(store, 'select').and.returnValue(of(initialState));
There is a Typo in expected. It should be 'b' instead of 'a'
const expected = hot('b', { b: outcome });
I havent been able to get a testing working with marbles yet.
Im using Nrwl nx, so my effects test looks like this:
import { TestBed } from '#angular/core/testing';
import { Subject, ReplaySubject } from 'rxjs';
import { EffectsModule } from '#ngrx/effects';
import { StoreModule } from '#ngrx/store';
import { provideMockActions } from '#ngrx/effects/testing';
import { NxModule } from '#nrwl/nx';
import { DataPersistence } from '#nrwl/nx';
import { ChangePasswordEffects } from './change-password.effects';
import { ChangePassword, ChangePasswordSuccessful } from './change-password.actions';
import { HttpClientTestingModule } from '#angular/common/http/testing';
describe('ChangePasswordEffects', () => {
let actions: Subject<any>;
let effects: ChangePasswordEffects;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NxModule.forRoot(), StoreModule.forRoot({}), EffectsModule.forRoot([]), HttpClientTestingModule],
providers: [ChangePasswordEffects, DataPersistence, provideMockActions(() => actions)]
});
effects = TestBed.get(ChangePasswordEffects);
});
describe('loadChangePassword$', () => {
it('should work', () => {
actions = new ReplaySubject(1);
actions.next(ChangePassword);
effects.loadChangePassword$.subscribe(result => {
expect(result).toEqual(ChangePasswordSuccessful);
});
});
});
});
And my code looks like this:
import { PasswordChangeError } from './../../models/password-change-error';
import { Injectable } from '#angular/core';
import { Effect, Actions } from '#ngrx/effects';
import { DataPersistence } from '#nrwl/nx';
import { ChangePasswordPartialState } from './change-password.reducer';
import {
ChangePassword,
ChangePasswordSuccessful,
ChangePasswordError,
ChangePasswordActionTypes
} from './change-password.actions';
import { ChangePasswordService } from '../../services/change-password/change-password.service';
import { map } from 'rxjs/operators';
#Injectable()
export class ChangePasswordEffects {
#Effect() loadChangePassword$ = this.dataPersistence.fetch(ChangePasswordActionTypes.ChangePassword, {
run: (action: ChangePassword, state: ChangePasswordPartialState) => {
return this.passwordService
.changePassword(action.newPassword, action.userId)
.pipe(map(res => new ChangePasswordSuccessful(res)));
},
onError: (action: ChangePassword, error: PasswordChangeError) => {
return new ChangePasswordError(error);
}
});
constructor(
private actions$: Actions,
private dataPersistence: DataPersistence<ChangePasswordPartialState>,
private passwordService: ChangePasswordService
) {}
}

Angular5- setInterval whiten the page

I am adding notification to my project. The website should shown a number of notification numbers that the user have got without refreshing page. To do so i have used setInterval function inside ngOnInit but when i used it inside ngOnInit the page goes white and shows nothing but the timer is still running.
Here is how i have implement the code.
ngOnInit
ngOnInit() {
this.subscription = this.loginInfoService.getLoginChangeEmitter()
.subscribe(item => this.loginSuccess(item));
this.subscription = this.loginInfoService.getSiteNameEmitter()
.subscribe(item => this.getSiteName(item));
this.subscription = this.loginInfoService.getSiteDescriptionEmitter()
.subscribe(item => this.getSiteDescription(item));
if (innerWidth < 766) {
this.notificationBell = true;
} else {
this.notificationBell = false;
}
//if i add this line page goes white
setInterval(() => {
this.getAllNotification();
}, 3000);
}
Code to get all notification
getAllNotification() {
this.unSeenNotification = [];
this.notificationService.getAllNotifications().subscribe(
result => {
this.notificationModel = result;
this.showLoading = false;
result.forEach(result => {
if (!result.isSeen) {
this.unSeenNotification.push(result);
}
})
this.notificationCount = this.unSeenNotification.length;
},
error => {
this.alertService.error(error, "Error!");
});
}
You can below window interval:
export class FoobarComponent implements OnInit {
static intervalId: Number;
ngOnInit(): void {
this.startInterval();
}
startInterval() void {
if(FoobarComponent.intervalId) { // always undefined
window.clearInterval(this.intervalId);
}
FoobarComponent.intervalId = window.setInterval(() => {
console.log('Hi'); }, 1000);
}
}

How to correctly test effects in ngrx 4?

There are plenty of tutorials how to test effects in ngrx 3.
However, I've found only 1 or 2 for ngrx4 (where they removed the classical approach via EffectsTestingModule ), e.g. the official tutorial
However, in my case their approach doesn't work.
effects.spec.ts (under src/modules/list/store/list in the link below)
describe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async() => {
const item = makeItem(Faker.random.word);
actions = hot('--a-', { a: new AddItem({ item })});
const expected = cold('--b', { b: new AddUpdateItemSuccess({ item }) });
// comparing marbles
expect(effects.addItem$).toBeObservable(expected);
});
})
effects.ts (under src/modules/list/store/list in the link below)
...
#Effect() addItem$ = this._actions$
.ofType(ADD_ITEM)
.map<AddItem, {item: Item}>(action => {
return action.payload
})
.mergeMap<{item: Item}, Observable<Item>>(payload => {
return Observable.fromPromise(this._listService.add(payload.item))
})
.map<any, AddUpdateItemSuccess>(item => {
return new AddUpdateItemSuccess({
item,
})
});
...
Error
should return LoadItemsSuccess action for each item
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'N', value: AddUpdateItemSuccess({ payload: Object({ item: Object({ title: Function }) }), type: 'ADD_UPDATE_ITEM_SUCCESS' }), error: undefined, hasValue: true }) }).
at compare (webpack:///node_modules/jasmine-marbles/index.js:82:0 <- karma-test-shim.js:159059:33)
at Object.<anonymous> (webpack:///src/modules/list/store/list/effects.spec.ts:58:31 <- karma-test-shim.js:131230:42)
at step (karma-test-shim.js:131170:23)
NOTE: the effects use a service which involves writing to PouchDB. However, the issue doesn't seem related to that
and also the effects work in the running app.
The full code is a Ionic 3 app and be found here (just clone, npm i and npm run test)
UPDATE:
With ReplaySubject it works, but not with hot/cold marbles
const item = makeItem(Faker.random.word);
actions = new ReplaySubject(1) // = Observable + Observer, 1 = buffer size
actions.next(new AddItem({ item }));
effects.addItem$.subscribe(result => {
expect(result).toEqual(new AddUpdateItemSuccess({ item }));
});
My question was answered by #phillipzada at the Github issue I posted.
For anyone checking this out later, I report here the answer:
Looks like this is a RxJS issue when using promises using marbles. https://stackoverflow.com/a/46313743/4148561
I did manage to do a bit of a hack which should work, however, you will need to put a separate test the service is being called unless you can update the service to return an observable instead of a promise.
Essentially what I did was extract the Observable.fromPromise call into its own "internal function" which we can mock to simulate a call to the service, then it looks from there.
This way you can test the internal function _addItem without using marbles.
Effect
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
export const ADD_ITEM = 'Add Item';
export const ADD_UPDATE_ITEM_SUCCESS = 'Add Item Success';
export class AddItem implements Action {
type: string = ADD_ITEM;
constructor(public payload: { item: any }) { }
}
export class AddUpdateItemSuccess implements Action {
type: string = ADD_UPDATE_ITEM_SUCCESS;
constructor(public payload: { item: any }) { }
}
export class Item {
}
export class ListingService {
add(item: Item) {
return new Promise((resolve, reject) => { resolve(item); });
}
}
#Injectable()
export class SutEffect {
_addItem(payload: { item: Item }) {
return Observable.fromPromise(this._listService.add(payload.item));
}
#Effect() addItem$ = this._actions$
.ofType<AddItem>(ADD_ITEM)
.map(action => action.payload)
.mergeMap<{ item: Item }, Observable<Item>>(payload => {
return this._addItem(payload).map(item => new AddUpdateItemSuccess({
item,
}));
});
constructor(
private _actions$: Actions,
private _listService: ListingService) {
}
}
Spec
import { cold, hot, getTestScheduler } from 'jasmine-marbles';
import { async, TestBed } from '#angular/core/testing';
import { Actions } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { getTestActions, TestActions } from 'app/tests/sut.helpers';
import { AddItem, AddUpdateItemSuccess, ListingService, SutEffect } from './sut.effect';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
describe('Effect Tests', () => {
let store: Store<any>;
let storeSpy: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({})
],
providers: [
SutEffect,
{
provide: ListingService,
useValue: jasmine.createSpyObj('ListingService', ['add'])
},
{
provide: Actions,
useFactory: getTestActions
}
]
});
store = TestBed.get(Store);
storeSpy = spyOn(store, 'dispatch').and.callThrough();
storeSpy = spyOn(store, 'select').and.callThrough();
}));
function setup() {
return {
effects: TestBed.get(SutEffect) as SutEffect,
listingService: TestBed.get(ListingService) as jasmine.SpyObj<ListingService>,
actions$: TestBed.get(Actions) as TestActions
};
}
fdescribe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async () => {
const { effects, listingService, actions$ } = setup();
const action = new AddItem({ item: 'test' });
const completion = new AddUpdateItemSuccess({ item: 'test' });
// mock this function which we can test later on, due to the promise issue
spyOn(effects, '_addItem').and.returnValue(Observable.of('test'));
actions$.stream = hot('-a|', { a: action });
const expected = cold('-b|', { b: completion });
expect(effects.addItem$).toBeObservable(expected);
expect(effects._addItem).toHaveBeenCalled();
});
})
})
Helpers
import { Actions } from '#ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
export class TestActions extends Actions {
constructor() {
super(empty());
}
set stream(source: Observable<any>) {
this.source = source;
}
}
export function getTestActions() {
return new TestActions();
}