Express, Angular, Universal and how to get the base url using injection - angular-universal

How do I get the port number from express into Angular Universal, but still make the front end usable. (Angular 13)
So the basics of the setup. I have Angular Universal (the heros tutorial) loaded.
In the server.ts file I add this to my renderer:
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }, { provide: BASE_URL, useValue: req.get('host') }] });
The base url is defined in a new file:
export const BASE_URL = new InjectionToken<string>('BaseUrl');
Next, I need to import that base url into a component/service constructor:
#Inject(BASE_URL) private baseUrl: string
and then console log that out in my onInit or constructor...
console.log(this.baseUrl);
Run the whole thing and it gives me the number in the console where I am running my website.
Now, the front end of course crashes. (We don't know where to get the base url injection from, since we did not declare that as a provider to our app. Lets do that:
providers: [{ provide: BASE_URL, useValue: "test"}]
ok, Now... we run it, and suddenly we get "test" in our console for express, and the app works.
Is there a way to do both? Get the port from express and not bomb on the front end.
I want to get the base url when running SSR, but get an empty string when running in a browser.

The correct answer is:
Do not use a provider in app.module.ts and use the optional keyword when injecting it into your constructor/service
#Optional() #Inject(BASE_URL) private baseUrl: string

Related

inject nestjs service to build context for graphql gateway server

In app.module.ts I have the following:
#Module({
imports: [
...,
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
server: {
context: getContext,
},
driver: ApolloGatewayDriver,
gateway: {
buildService: ({ name, url }) => {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }: any) {
...
},
});
},
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'iam', url: API_URL_IAM },
],
})
},
}),
]
...
})
here getContext is just a regular function which is not part of nestjs context (doesn't have injection, module capability) like below:
export const getContext = async ({ req }) => {
return {}
}
Is there any way to use nestjs services instead of plain old functional approach to build the context for graphql gateway in nestjs?
Thanks in advance for any kind of help.
I believe you're looking to create a service that is #Injectable and you can use that injectable service via a provider. What a provider will do is satisfy any dependency injection necessary.
In your scenario, I would import other modules as necessary. For building context, I would create a config file to create from env variables. Then create a custom provider that reads from the env variables and provides that implementation of the class/service to the other classes as their dependency injection.
For example, if I have a graphQL module. I would import the independent module. Then, I would provide in the providers section, the handler/service classes and the dependencies as an #injectable. Once your service class is created based on your config (which your provider class would handle), you would attach that service class to your GraphQL class to maybe lets say direct the URL to your dev/prod envs.

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

Using SocketIo Manager with a default URL

My goal is to add a token in the socketio reconnection from the client (works fine on the first connection, but the query is null on the reconnection, if the server restarted while the client stayed on).
The documentation indicates I need to use the Manager to customize the reconnection behavior (and add a query parameter).
However, I'm getting trouble finding how to use this Manager: I can't find a way to connect to the server.
What I was using without Manager (works fine):
this.socket = io({
query: {
token: 'abc',
}
});
Version with the Manager:
const manager = new Manager(window.location, {
hostname: "localhost",
path: "/socket.io",
port: "8080",
query: {
auth: "123"
}
});
So I tried many approaches (nothing, '', 'http://localhost:8080', 'http://localhost:8080/socket.io', adding those lines to the options:
hostname: "localhost",
path: "/socket.io",
port: "8080" in the options,
But I couldn't connect.
The documentation indicates the default URL is:
url (String) (defaults to window.location)
For some reasons, using window.location as URL refreshes the page infinitely, no matter if I enter it as URL in the io() creator or in the new Manager.
I am using socket.io-client 3.0.3.
Could someone explain me what I'm doing wrong ?
Thanks
Updating to 3.0.4 solved the initial problem, which was to be able to send the token in the initial query.
I also found this code in the doc, which solves the problem:
this.socket.on('reconnect_attempt', () => {
socket.io.opts.query = {
token: 'fgh'
}
});
However, it doesn't solve the problem of the Manager that just doesn't work. I feel like it should be removed from the doc. I illustrated the problem in this repo:
https://github.com/Yvanovitch/socket.io/blob/master/examples/chat/public/main.js

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.

Resources