hey i have upgraded my project from angular 4 to angular 7 and some of the services, modules are deprecated.
this is my app.module.ts
#NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
SharedModule,
HttpClientModule,
AppRoutingModule,
NgbModule.forRoot(),
StoreModule.forRoot({})
],
providers: [
SessionTimeoutService,
SpinnerService,
{
provide: HttpClient,
useFactory: httpFactory,
deps: [XHRBackend, RequestOptions, Store, SpinnerService]
},
UtilService,
{ provide: NgbDateParserFormatter, useClass: DateParserFormatter }
],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private injector: Injector) {
ServiceLocatorService.injector = this.injector;
}
}
now XHRBackend, RequestOptions are now deprecated and giving me error
can someone tell me how to resolve it?
and this is my Http interceptor file
#Injectable()
export class InterceptedHttp extends HttpClient {
constructor(
backend: HttpBackend,
defaultOptions: RequestOptions,
private store: Store<any>,
private spinnerService: SpinnerService
) {
super(backend, defaultOptions);
}
request(
url: string | HttpRequest,
options?: RequestOptionsArgs
): Observable<HttpResponse> {
this.showLoader();
return this.tryCatch(super.request(url, options)).finally(() => {
this.hideLoader();
});
}
get(url: string, options?: RequestOptionsArgs): Observable<HttpResponse> {
url = this.updateUrl(url);
return this.tryCatch(super.get(url, this.getRequestOptionArgs(options)));
}
post(
url: string,
body: string,
options?: RequestOptionsArgs
): Observable<HttpResponse> {
url = this.updateUrl(url);
return this.tryCatch(
super.post(url, body, this.getRequestOptionArgs(options))
);
}
put(
url: string,
body: string,
options?: RequestOptionsArgs
): Observable<HttpResponse> {
url = this.updateUrl(url);
return this.tryCatch(
super.put(url, body, this.getRequestOptionArgs(options))
);
}
delete(url: string, options?: RequestOptionsArgs): Observable<HttpResponse> {
url = this.updateUrl(url);
return this.tryCatch(super.delete(url, this.getRequestOptionArgs(options)));
}
patch(
url: string,
body: any,
options?: RequestOptionsArgs
): Observable<HttpResponse> {
url = this.updateUrl(url);
return this.tryCatch(
super.patch(url, body, this.getRequestOptionArgs(options))
);
}
private updateUrl(req: string) {
return environment.origin + req;
}
private getRequestOptionArgs(
options?: RequestOptionsArgs
): RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new HttpHeaders();
}
options.headers.append("Content-Type", "application/json");
options.headers.append(
"Authorization",
` Bearer ${sessionStorage.AccessToken}`
);
return options;
}
}
i am getting errors
RequestOptions,
RequestOptionsArgs,
these are deprecated now i am getting errors how to resolve it?
You have to use new packge from #angular/common/http
like
import { HttpClientModule } from "#angular/common/http";
import { HttpClient } from "#angular/common/http";
So mostly you will use HttpHeaders to construct your ajax header like params formdata etc...
Headers -> HttpHeaders
Response -> HttpResponse
RequestOptions, RequestOptionsArgs are remove and you have to use HttpParams
Please read new change log here
Related
I've created a module factory function with returns a class annotated by #Module({}). My problem is, the class depends on function arguments providerToken and strategy so I cannot move it outside the function. When I run the code, it works perfectly fine but the value for forRoot and forRootAsync is not properly checked for types. Infact typescript doesn't throw any error about that. Also what should be my return value of the function. I've put it any to avoid errors for now.
This is how I'm using the function to create a module
const TwitterAuthModule =
createHybridAuthModule<TwitterAuthModuleOptions>(
TWITTER_HYBRID_AUTH_OPTIONS,
TwitterAuthStrategy
);
Module creator factory
export function createHybridAuthModule<T>(
providerToken: string,
strategy: any
): any {
#Module({})
class NestHybridAuthModule {
static forRoot(options: T): DynamicModule {
return {
module: NestHybridAuthModule,
providers: [
{
provide: providerToken,
useValue: options,
},
strategy,
],
};
}
static forRootAsync(
options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
): DynamicModule {
return {
module: NestHybridAuthModule,
providers: [...this.createAsyncProviders(options), strategy],
};
}
private static createAsyncProviders(
options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
}
const useClass = options.useClass as Type<ModuleOptionsFactory<T>>;
return [
this.createAsyncOptionsProvider(options),
{
provide: useClass,
useClass,
},
];
}
private static createAsyncOptionsProvider(
options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
): Provider {
if (options.useFactory) {
return {
provide: providerToken,
useFactory: options.useFactory,
inject: options.inject || [],
};
}
const inject = [
(options.useClass || options.useExisting) as Type<
ModuleOptionsFactory<T>
>,
];
return {
provide: providerToken,
useFactory: async (optionsFactory: ModuleOptionsFactory<T>) =>
await optionsFactory.createModuleOptions(),
inject,
};
}
}
return NestHybridAuthModule;
}
Usage of the created module. The value of forRoot is never checked for types
#Module({
imports: [
TwitterAuthModule.forRoot({
consumerKey: '********',
consumerSecret: '******',
callbackURL: '*******',
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
I finally found my answer at https://stackoverflow.com/a/43674389/1029506 and implemented it using a customer decorator:
export interface INestHybridAuthModule<T> {
forRoot(options: T): DynamicModule;
}
function staticImplements<T>() {
return <U extends T>(constructor: U) => {
constructor;
};
}
function createHybridAuthModule<T>(
providerToken: string,
strategy: any
): INestHybridAuthModule<T> {
#Module({})
#staticImplements<INestHybridAuthModule<T>>()
class NestHybridAuthModule {
static forRoot(options: T): DynamicModule {
// code here
}
static forRootAsync(
options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
): DynamicModule {
// Code here
}
private static createAsyncProviders(
options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
): Provider[] {
// Code here
}
private static createAsyncOptionsProvider(
options: ModuleAsyncOptions<ModuleOptionsFactory<T>, T>
): Provider {
// Code here
}
}
return NestHybridAuthModule;
}
And Finally
const TwitterAuthModule: INestHybridAuthModule<TwitterAuthModuleOptions> = createHybridAuthModule<
TwitterAuthModuleOptions
>(TWITTER_HYBRID_AUTH_OPTIONS, TwitterAuthStrategy);
Reason why typescript not check your types is that it treat NestHybridAuthModule as any because createHybridAuthModule function return type is any. Remove it and allow typescript infer return type.
In case there is --noImplicitReturns used you can use generic interface
Edit:
Thanks for pointing it.
corrected example:
interface INestHybridAuthModule<T> {
new(...args: any[]): any,
forRoot(options: T): DynamicModule
}
export function createHybridAuthModule<T>(
providerToken: string,
strategy: any
): INestHybridAuthModule<T> {
class NestHybridAuthModule {
static forRoot(options: T): DynamicModule {
// logic here
}
}
return NestHybridAuthModule
}
original:
simplified example:
interface INestHybridAuthModule<T> {
forRoot(options: T): DynamicModule
}
export function createHybridAuthModule<T>(
providerToken: string,
strategy: any
): new (...args: any[]) => INestHybridAuthModule<T> {
class NestHybridAuthModule {
forRoot(options: T): DynamicModule {
// logic here
}
}
return NestHybridAuthModule
}
When I use the import PolicyModule.forFeature more than one time, the next import of the PolicyModule overrides gates in PolicyStorage.
When I try to use PolicyProvider in CandidateModule's CandidateEducationService by calling PolicyProvider
await this.policy.denyAccessUnlessGranted('canDelete', education);
I get the exception Gate by entity 'CandidateEducationEntity' not found.
I output PolicyStorage in CandidateEducationService and got array gates with JobPolicy
PolicyStorage {
gates:
[ { policy: [Function: JobPolicy], entity: [Function: JobEntity] } ]
}
But I was expecting
PolicyStorage {
gates:
[ { policy: [Function: CandidateEducationPolicy], entity: [Function: CandidateEducationEntity] } ]
}
I created a dynamic module PolicyModule
#Module({})
export class PolicyModule {
public static forFeature(gates: PolicyGate[]): DynamicModule {
const providers: Provider[] = [
...gates.map(gate => gate.policy),
{
provide: PolicyStorage,
useValue: new PolicyStorage(gates),
},
PolicyProvider,
];
return {
module: PolicyModule,
imports: [
CommonModule,
],
providers,
exports: providers,
};
}
}
PolicyStorage
#Injectable()
export class PolicyStorage {
constructor(private gates: PolicyGate[]) {
console.log(this.gates);
}
public find(name: string): PolicyGate | null {
return this.gates.find(policy => policy.entity.name === name);
}
}
PolicyProvider
#Injectable()
export class PolicyProvider<E, P> {
constructor(
private readonly moduleRef: ModuleRef,
private readonly gateStorage: PolicyStorage,
private readonly appContext: AppContextService,
) {
}
public async denyAccessUnlessGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
if (await this.denies(methodNames, entity, customData)) {
throw new ForbiddenException();
}
}
public async allowAccessIfGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
const allowed = await this.allows(methodNames, entity, customData);
if (!allowed) {
throw new ForbiddenException();
}
}
private async allows(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === true);
}
private async denies(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === false);
}
private async getPolicyResults(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean[]> {
const methodNamesArray = Array.isArray(methodNames) ? methodNames : [methodNames];
const gate = this.findByClassName(entity.constructor.name);
const user = this.appContext.get('user');
const policy = await this.moduleRef.get<P>(gate.policy, {strict: false});
const results = [];
for (const methodName of methodNamesArray) {
results.push(!!await policy[methodName as string](entity, user, customData));
}
return results;
}
private findByClassName(name: string) {
const gate = this.gateStorage.find(name);
if (!gate) {
throw new RuntimeException(`Gate by entity '${name}' not found`);
}
return gate;
}
}
Using module in other module. Example:
JobsModule
#Module({
imports: [
TypeOrmModule.forFeature(
[
JobEntity,
],
),
PolicyModule.forFeature([
{
policy: JobPolicy,
entity: JobEntity,
},
]),
],
controllers: [
ManagerJobsController,
],
providers: [
ManagerJobsService,
],
})
export class JobsModule {
}
CandidateModule
#Module({
imports: [
TypeOrmModule.forFeature(
[
CandidateEducationEntity,
],
),
PolicyModule.forFeature([
{
policy: CandidateEducationPolicy,
entity: CandidateEducationEntity,
},
]),
],
controllers: [
CandidateEducationController,
],
providers: [
CandidateEducationService,
],
})
export class CandidateModule {
}
Update:
Nest v6 introduced request-scoped providers, see this answer.
All modules and its providers are singletons. If you register a provider under the same token twice within the same module, it will be overridden.
If you have a look at the TypeOrmModule you can see it registers its repository providers under a unique custom token for each entity:
export function getRepositoryToken(entity: Function) {
if (
entity.prototype instanceof Repository ||
entity.prototype instanceof AbstractRepository
) {
return getCustomRepositoryToken(entity);
}
return `${entity.name}Repository`;
}
So in your case, you could have the functions getPolicyProviderToken and getPolicyStorageToken and both register and inject your providers under these tokens that are unique for each importing module.
I write a http intercpetor,it is like this:
import { Injectable, Inject } from '#angular/core';
import { SlimLoadingBarService } from 'ng2-slim-loading-bar';
// ... other imports in here
#Injectable()
export class HttpInterceptorService extends Http {
private apiEndpoint: string;
constructor(
#Inject(CONFIG_TOKEN) config: Config,
private backend: ConnectionBackend,
private defaultOptions: RequestOptions,
private slimLoadingBarService: SlimLoadingBarService
) {
super(backend, defaultOptions);
this.apiEndpoint = config.apiEndpoint;
}
get(url: string, options?: RequestOptionsArgs): Observable<any> {
this.beforeRequest();
return super.get(this.getFullUrl(url), this.requestOptions(options))
// ...
}
private beforeRequest(): void {
// this is not work
this.slimLoadingBarService.start();
}
// ... other methods
}
My app.module provide config like this:
{
provide: HttpInterceptorService,
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => {
return new HttpInterceptorService(CONFIG, backend,
defaultOptions, new SlimLoadingBarService);
},
deps: [XHRBackend, RequestOptions]
}
Now, this Http interceptor is worker,But the SlimLoadingBarService doew not work.
I feel should be wrong to pass the new SlimLoadingBarService lead, but the direct transmission SlimLoadingBarService, the same will be given, and now stuck in this place do not know how to continue.
There is no error message, but loadingbar(SlimLoadingBarService) does not show up.
Try this instead.
{
provide: HttpInterceptorService,
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, slimLoadingBarService: SlimLoadingBarService) => {
return new HttpInterceptorService(CONFIG, backend,
defaultOptions, slimLoadingBarService);
},
deps: [XHRBackend, RequestOptions, SlimLoadingBarService]
}
I'm trying to get some mocked results for my development environment. I've tried to incorporate angular-in-memory-web-api without much success. Here's my code:
app.module.ts:
#NgModule({
declarations: [
AppComponent,
],
imports: [
...
HttpModule,
...
InMemoryWebApiModule.forRoot(MockEventData, {
passThruUnknownUrl: true
})
],
providers: [
...
{
provide: Http,
useClass: ExtendedHttpService
},
...
{
provide: EventService,
useFactory: (http: Http, userService: UserService, newEventService: NewEventService, router: Router) => {
if (environment.production) {
return new EventService(http, userService, newEventService, router)
} else {
return new MockEventService(http, userService, newEventService, router)
}
},
deps: [Http, UserService, NewEventService, Router]
}
],
bootstrap: [AppComponent]
})
export class AppModule {
mock-event.service.ts:
#Injectable()
export class MockEventService {
private imageUploadBatch: Observable<Boolean>[];
private fakeResponse;
constructor(
private http: Http,
private userService: UserService,
private newEventService: NewEventService,
private router: Router,
) {
};
getEvents(excludedEvents: string[]): Observable<Event[]> {
return this.http
.post('api/events', excludedEvents)
.map((res: Response) => res.json())
.publishLast().refCount()
.catch((error: any) => Observable.throw(error.json().error || 'Show error.'));
}
}
mock-event-data.ts:
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class MockEventData implements InMemoryDbService {
createDb() {
let events = [
{ id: 1, name: 'Windstorm' },
{ id: 2, name: 'Bombasto' },
{ id: 3, name: 'Magneta' },
{ id: 4, name: 'Tornado' }
];
return { events };
}
}
The code is quite simple. I made it following this guide: https://angular.io/docs/ts/latest/guide/server-communication.html. However, for whatever reason, the POST for /events always returns {data: Array[0]}.
Any help provided will be deeply appreciated.
Thanks!
post method will NOT just retrieve the data to angular-in-memory-web-api. Instead it will create the entity accordingly. It's essential and default behavior is to send data to the server same as put and delete. Of course there will be a response to the post request which is probably the data in angular-in-memory-web-api case but remember, this totally depends on the server to response. On the otherget should be used for the purpose of retrieving data from angular-in-memory-web-api as you want.
Well, as it turns out, POST seems to not return data in angular-in-memory-web-api. I succeeded in retrieving data by using a GET request. This isn't ideal, but it'll have to work for now.
If someone has a better answer, please provide it, as it's a bit iffy to commit a mock that doesn't use the original request types.
Thanks!
Below error shown when I ran ng test command.
Here is my service spec,
describe('BackendService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: Http, useFactory: (backend, options) => {
return new Http(backend, options);
},
deps: [MockBackend, BaseRequestOptions]
},
MockBackend,
BaseRequestOptions,
BackendService
]
});
});
it('should ...', inject([BackendService, MockBackend], (service: BackendService) => {
expect(service).toBeTruthy();
})
);
});
BackendService.ts looks like,
export class BackendService {
private baseUrl: string = 'https://foo-backend.appspot.com/_ah/api/default/v1';
constructor(private http: Http, baseName: string) {
this.baseUrl = this.baseUrl + baseName;
}
.....
}
It seems like extra parameter inside the BackendService class's constructor causes this problem..
How do you expect Angular to know what baseName is supposed to be? All constructor parameters need to be obtained from the Injector. And if there is no corresponding token for the parameter, then it can't be looked up.
You can add a token by doing
// somewhere in some file
import { OpaqueToken } from '#angular/core';
export const BASE_NAME_TOKEN = new OpaqueToken("app.base_name");
// in test class
import { BASE_NAME_TOKEN } from 'where-ever'
TestBed.configureTestingModule({
providers: [
BackendService,
{ provide: BASE_NAME_TOKEN, useValue: 'whatever-the-base-is' }
]
});
// in service constructor
import { Inject } from '#angular/core'
import { BASE_NAME_TOKEN } from 'where-ever'
constructor(http: Http, #Inject(BASE_NAME_TOKEN) baseName: string) {}
See Also:
Dependency Injection Tokens
What is the difference between #Inject vs constructor injection as normal parameter in Angular 2?