NGXS: How to get the RouteHandler to work? - ngxs

When creating a ngxs RouteHandler according to the documentation found here:
https://www.ngxs.io/advanced/action-handlers
I get a NullInjectorError when I add:
providers: [
{
provide: APP_INITIALIZER,
useFactory: noop,
deps: [RouteHandler],
multi: true
}
]
Has anyone successfully dispatched routes from an ngxs action using this documented RouteHandler?

This one stumped me for a little bit.
What I found, at least in my case, is that the docs failed to mention you need to set { providedIn: 'root' } in the "service" - so to speak.
#Injectable({ providedIn: 'root' });
Everything else was pretty much the same as explained in the docs.
I also (quickly) made a stackblitz which should give you a general idea as to how things should be set up.
https://stackblitz.com/github/baxelson12/ngxs-route-handlers

Related

Angularfire conditionally provide appcheck

I have two angular projects:
Main app
Webcomponent (angular elements)
Webcomponent is used in the main app. Both are using angularfire for executing Firebase functions, working with Firestore and more.
Also I am enforcing verified request to the Functions and Firestore by AppCheck.
The web component needs to work separately. To be able to request Firebase servers I need to provide the AppCheck in both projects like this:
#NgModule({
...
imports: [
...
provideAppCheck(() => initializeAppCheck(getApp(), {
provider: new ReCaptchaV3Provider(environment.firebase.appCheck.recaptcha3SiteKey),
isTokenAutoRefreshEnabled: environment.firebase.appCheck.isTokenAutoRefreshEnabled,
}))
...
],
...
})
This works just fine when webcomponent is not included in the main app. However when so, the AppCheck is initialized two times and it throws an error:
Unhandled Promise rejection: reCAPTCHA has already been rendered in this element ; Zone: <root> ; Task: Promise.then ; Value: Error: reCAPTCHA has already been rendered in this element
So the webcomponent needs to check if appcheck already exists in document and add it only if it does not. I tried to work with appCheckInstance$ but that is an observable and provideAppCheck requires only AppCheck type.
When I try to move provideAppCheck to component which would handle the logic, I get an error saying that calling it can not be done outside module:
Either AngularFireModule has not been provided in your AppModule (this can be done manually or implictly using
provideFirebaseApp) or you're calling an AngularFire method outside of an NgModule (which is not supported).
I have no other ideas how this could be done other than building two webcomponents (one with appcheck, other without), but thats just not an option.
It turned out that the problem was elsewhere. I thought that conditional appcheck loading would help, but it didn't, because then angularfire(in webcomponent) didn't use the appcheck that the main app initialized. And hence connections to firebase were blocked (as if there was no appcheck initialized).
Solution I've figured out that works:
In webcomponent initialize all firebase services under different name.
So instead of:
#NgModule({
...
imports: [
...
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => getFirestore(getApp())),
provideAppCheck(() => initializeAppCheck(getApp(), {
provider: new ReCaptchaV3Provider(environment.firebase.appCheck.recaptcha3SiteKey),
isTokenAutoRefreshEnabled: environment.firebase.appCheck.isTokenAutoRefreshEnabled,
})),
...
],
...
})
do:
#NgModule({
...
imports: [
...
provideFirebaseApp(() => initializeApp(environment.firebase, 'webcomponent-app')),
provideFirestore(() => getFirestore(getApp('webcomponent-app'))),
provideAppCheck(() => initializeAppCheck(getApp('webcomponent-app'), {
provider: new ReCaptchaV3Provider(environment.firebase.appCheck.recaptcha3SiteKey),
isTokenAutoRefreshEnabled: environment.firebase.appCheck.isTokenAutoRefreshEnabled,
})),
...
],
...
})
This will initialize two instances (one for main app, other for webcomponent) with different names. And now initializing two appchecks is not problematic.

Spartacus Storefront Multisite I18n with Backend

We've run into some problems for our MultiSite Spartacus setup when doing I18n.
We'd like to have different translations for each site, so we put these on an API that can give back the messages dependent on the baseSite, eg: backend.org/baseSiteX/messages?group=common
But the Spartacus setup doesn't let us pass the baseSite? We can
pass {{lng}} and {{ns}}, but no baseSite.
See https://sap.github.io/spartacus-docs/i18n/#lazy-loading
We'd could do it by overriding i18nextInit, but I'm unsure how to achieve this.
In the documentation, it says you can use crossOrigin: true in the config, but that does not seem to work. The type-checking say it's unsupported, and it still shows uw CORS-issues
Does someone have ideas for these problems?
Currently only language {{lng}} and chunk name {{ns}} are supported as dynamic params in the i18n.backend.loadPath config.
To achieve your goal, you can implement a custom Spartacus CONFIG_INITIALIZER to will populate your i18n.backend.loadPath config based on the value from the BaseSiteService.getActive():
#Injectable({ providedIn: 'root' })
export class I18nBackendPathConfigInitializer implements ConfigInitializer {
readonly scopes = ['i18n.backend.loadPath']; // declare config key that you will resolve
readonly configFactory = () => this.resolveConfig().toPromise();
constructor(protected baseSiteService: BaseSiteService) {}
protected resolveConfig(): Observable<I18nConfig> {
return this.baseSiteService.getActive().pipe(
take(1),
map((baseSite) => ({
i18n: {
backend: {
// initialize your i18n backend path using the basesite value:
loadPath: `https://backend.org/${baseSite}/messages?lang={{lng}}&group={{ns}}`,
},
},
}))
);
}
}
and provide it in your module (i.e. in app.module):
#NgModule({
providers: [
{
provide: CONFIG_INITIALIZER,
useExisting: I18nBackendPathConfigInitializer,
multi: true,
},
],
/* ... */
})
Note: the above solution assumes the active basesite is set only once, on app start (which is the case in Spartacus by default).

How can I pass REDIS_URI for NestJS cache manager?

In the official documentation this is the correct way to use the cache manager with Redis:
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '#nestjs/common';
import { AppController } from './app.controller';
#Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
controllers: [AppController],
})
export class AppModule {}
Source: https://docs.nestjs.com/techniques/caching#different-stores
However, I did not find any documentation on how to pass Redis instance data using REDIS_URI. I need to use it with Heroku and I believe this is a common use case.
EDIT:
now they are type-safe: https://github.com/nestjs/nest/pull/8592
I've exploring a bit about how the redis client is instantiated. Due to this line I think that the options that you've passed to CacheModule.register will be forwarded to Redis#createClient (from redis package). Therefore, you can pass the URI like:
CacheModule.register({
store: redisStore,
url: 'redis://localhost:6379'
})
try this and let me know if it works.
edit:
Explaining how I got that:
Taking { store: redisStore, url: '...' } as options.
Here in CacheModule.register I found that your options will live under CACHE_MODULE_OPTIONS token (as a Nest provider)
Then I search for places in where this token will be used. Then I found here that those options were passed to cacheManager.caching. Where cacheManager is the module cache-manager
Looking into to the cacheManager.caching's code here, you'll see that your options is now their args parameter
Since options.store (redisStore) is the module exported by cache-manager-redis-store package, args.store.create method is the same function as in redisStore.create
Thus args.store.create(args) is the same as doing redisStore.create(options) which, in the end, will call Redis.createClient passing this options

Angular in-memory-web-api method always returns 404 NotFound in the brower's console even if the tests passed

I'm new to unit testing in Angular (using Jasmine and Karma)
I'm trying to create some tests for my httpService, apparently the tests are OK.
But sometimes when I either run ng test, or refresh the browser, I found that one of the test in one of the 3 test suites has failed with this message : Uncaught [object Object] thrown.
Another annoying thing is that no matter whether all of the tests pass or any of them fail, if you check the browser's console, you'll ALWAYS find this message :
I'm attaching the code in a zip file (uploaded to Drive). You only need to run npm install and npm start.
I really hope you can help me understand why this testing behaves like a Russian roulette.
The issue is calculator.component.spec.ts. You are not mocking loanService where it is going out and making HTTP calls. You should always mock external services.
Change calculator.component.spec.ts to:
import { NO_ERRORS_SCHEMA } from '#angular/core';
import { FormBuilder } from '#angular/forms';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { CalculatorComponent } from './calculator.component';
import { LoanService } from '../loan.service';
import { Campaign } from '../campaign';
import { of } from 'rxjs/internal/observable/of';
describe('CalculatorComponent', () => {
let component: CalculatorComponent;
let fixture: ComponentFixture<CalculatorComponent>;
let mockLoanService: any;
beforeEach(async(() => {
// mockLoanService object, first parameter ('loanService') is optional, second paramter => array of methods needing
// mock for component
mockLoanService = jasmine.createSpyObj('loanService', ['getCurrentCampaign', 'getMonthlyAmount']);
TestBed.configureTestingModule({
declarations: [ CalculatorComponent ],
imports: [],
// NO_ERRORS_SCHEMA to ignore child components, if you need the
// painting of the DOM of the child components/directives, put them in declarations
schemas: [NO_ERRORS_SCHEMA],
providers: [
FormBuilder,
// provide the mock for LoanService
{ provide: LoanService, useValue: mockLoanService },
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CalculatorComponent);
component = fixture.componentInstance;
// getCurrentCampaig is related to ngOnInit so we have to mock it
mockLoanService.getCurrentCampaign.and.returnValue(of({
id: 1,
campaign_name: 'Donald Trump 2020',
min_quota: -200000000,
max_quota: 0,
max_amount: 0,
min_amount: 0,
tea: 1,
payment_date: new Date(),
currency: 'Fake News',
} as Campaign))
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I have written some comments in the file itself. By the way, Donald Trump 2020 and Fake News are just jokes, I have no political affiliation but I like writing jokes in my unit tests for other developers :).
Some notes:
1.) Whenever you are injecting a service, always mock it. You are testing the component and component alone, you have to assume that the service will do its job because it is already being tested.
2.) Check out NO_ERRORS_SCHEMA. It basically ignores all components/directives in your HTML that is not in the declarations array. If you are writing a test where you click the button of a child component and it affects this component, then declare it in declarations (basically if you need the actual implementation of the child component, declare it). Otherwise, use NO_ERRORS_SCHEMA.
3.) Importing SharedModule in all unit tests is not good in my opinion. It will make your unit tests slow. Instead, take advantage of declarations and providers and give the component what it needs and JUST what it needs (not extra stuff).
4.) A really good class in PluralSight called Unit Testing in Angular.
Taking that class, you will have a better understanding of Unit/Integration testing. Maybe buy a subscription to PluralSight or start a free trial.

Can't resolve all parameters for UIRouter: (?, ?)

I am using angular2 and uirouter for routing. I have successfully implemented uirouter module in application.But the problem arises when i try to test my application. Where i am using karma, Jasmin and initiating it using npm test. but encountered with ERROR:Can't resolve all parameters for UIRouter: (?, ?).
I have imported "UIRouter" in *.spec.ts file and added it in providers array as below.
import { UIRouterModule } from '#uirouter/angular';
import { UIRouter } from "#uirouter/core";
describe('Footer Menus', () => {
let footerMenuBlServiceRef:FooterMenuBLService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [],
providers: [UIRouter],
})
.compileComponents();
}));
But no luck. Any help will be appreciated.
Solved it !!!
Just remove the UIRouter from providers array but keep the import statement for it. and yes its working.
On last friday, I finally found a way to make it work. It is not a clean way to do it but it is working, at least.
I reimport the UIRouter.forRoot with the states of my feature module and I provide the APP_BASE_HREF value for the Root Provider of UIRouter.
Here is my BeforeEach :
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
HomeComponent,
HomeCardComponent
],
imports: [
AppMaterialModule,
// Always import root UIRouter module when testing a component including routing
UIRouterModule.forRoot({states: HOME_STATES})
],
providers: [
// Also, include the base href value when testing a component including routing
{provide: APP_BASE_HREF, useValue: '/'}
],
}).compileComponents();
}));
If you know a better way, I would be happy to know! :)
The UI-Router source can be of some help. This is what worked for me:
import { UIRouterModule } from '#uirouter/angular';
...
TestBed.configureTestingModule({
...
imports: [UIRouterModule.forRoot({ useHash: true })],
})

Resources