I wonder what is the right way to use http service. For example all my requests to server starts with /Api.
Should I write this.http.get('/Api/SomeRoute/:id') every time, or more elegant way exists to omit Api?
Or should I create some other managerService which will use http?
Something like an endpoint url is probably a good example for a general configuration file.
In my angular 2 apps i use the dependency injection for it. In this case i have something like an app.config.ts using OpaqueToken to make it injectable:
import { OpaqueToken } from '#angular/core';
export interface IAppConfig {
appTitle: string;
endpointUrl: string;
};
export const CONFIG: IAppConfig = {
appTitle: 'MyApp',
endpointUrl: 'http://api.myrealservice.com/'
};
export const LOCALCONFIG: IAppConfig = {
appTitle: 'MyApp (LOCAL)',
endpointUrl: 'http://localhost:8080/api/'
};
export let APP_CONFIG = new OpaqueToken('app.config');
This way you can have several configs (for example for local development or production etc..) and define this as a provider in your module like this:
providers: [
...,
{
provide: APP_CONFIG,
useValue: CONFIG
},
...
]
Then i just inject this config wherever i may need it, for example in a backend service to use the endpointUrl:
...
constructor(#Inject(APP_CONFIG) private _config:Config) {
console.log(this._config.endpointUrl);
}
In your module you can just change the kind of Config you want to provide (in this example CONFIG or LOCALCONFIG for example) and don't have to change it anywhere else anymore.
Hope this helps.
After your edit, you added a second question Or should I create some other managerService which will use http?:
Short answer: yes. You should seperate logic of your components, services, etc. as much as possible.
Let's say you have a an API which serves information about cats and dogs. What you want to have in your frontend would probably be a CatService, a DogService and a BackendService or ApiService, whatever you want to call it.
The CatService and DogService are the ones your view components will interact with, for example they will have methods like getCatById() or something like that. These services will most likely interact with anohter service, the BackendService which will inject Http and use general methods like post(), get() and so on.
So your BackendService is the one who has to know the specific urls, handle the responses or errors and report back to the calling services with the results, while the other ones will just be used as a pipeline and handle specific business logic.
You are right, it is better to put repeating values in a variable. You can put the base URL in a const:
const apiUrl = '/Api/';
and then if there is a change to the URL, you only change it in one place, and also your code looks cleaner. The usage is like this:
this.http.get(apiUrl + 'SomeRoute/:id')
Of course you can also create a function that does that for you, but that may be too much abstraction.
Related
I'm trying to create a module with a service and would like to create a custom Inject decorator for it.
Let's call the Module a Store module, which provides and exports a StoreService and has to have a name. This is the Module:
import { Module } from '#nestjs/common';
import { StoreService } from './store.service';
#Module({})
export class StoreModule {
static register(
name: string,
): DynamicModule {
return {
module: StoreModule,
providers: [
{
provide: STORE_NAME,
useValue: name,
},
StoreService,
],
controllers: [],
exports: [StoreService],
};
}
}
The 'name' is what makes the store unique.
Now I'd like to create a decorator that fetches the store by name, like #InjectStore(name). I've tried to create a token based on the name, like STORE_SERVICE_${name} and injecting that, but the StoreModule needs to be registerred globally for that.
When I set global: true, it causes weird behaviour inside the module itself. Because StoreService is registered multiple times for multiple stores. For example, when I have a store cats and a store dogs, and I create a service inside the module and inject the store via constructor(private readonly store: StoreService), it'll get one of the two.
A workaround for this behaviour is probably to use the #InjectStore decorator, but because I came across this behaviour, I'm wondering if I'm missing something or if there's a better solution, because I want to make sure it injects the right store.
I've searched far and wide for an example implementation in NestJS modules, but just couldn't find what I was looking for.
Thanks in advance!
I was wondering whether I can use a factory to initialize a controller and then add it to a module. Code could look something like this, but this is not working:
const controllerFactory = {
provide: DefinitionController,
useFactory: async (service: DefinitionService) => {
//initialization of controller
return new DefinitionController();
},
inject: [DefinitionService],
};
#Module({
controllers: [controllerFactory],
providers: [DefinitionService],
})
export class DefinitionModule {}
It looks like using factories for controllers is not supported, but I am not sure. There is an example of using factory for providers, but I cannot find anything for controller in documentation or on google.
It's not possible to define your controller with an async factory comparable to custom providers. You cannot add dynamic endpoints/routes unless using the native express/fastify instance:
At the moment there is no way to register a route dynamically except
by using the internal HTTP / Fastify / Express instance
There is an issue where a dynamic routing module is discussed but this will probably not be part of nest very soon:
At the moment both Kamil and I are really busy, so this issue may take
some time - except someone else takes on the task :)
But you can use the OnModuleInit lifecycle event to do static initialization:
#Injectable()
export class DefinitionController implements OnModuleInit {
onModuleInit() {
console.log(`Initialization...`);
}
It will be called once when your app starts and has access to the injected providers in your controller, e.g. your DefinitionService.
Wanted to know is there any way that we can run one angular 6 app on with two rendering URL ?
Example :
www.domain.com/abc1
www.domain.com/abc2
We have two urls that will be using the same application but the difference is whenever abc1 is rendered data shown is different then the abc2.
Both are sharing the same code/application.
Not sure how to do this or that's possible ?
If the data comes from backend and the Angular apps differ in datasource you should try this:
ng build --prod --base-href "/abc1/"
ng build --prod --base-href "/abc2/"
you can create different layouts based on the view you want to load.
An alternative approach to the others listed would be to actually use the same app for this, just on different routes.
Say you are listing Todos, but on /abc1 they com from server-1.com/api, and on /abc2, they come from server-2.com/api.
You'd first create your app routing module, to capture the "subdomain" (as you called it, although it's actually a virtual directory) part of the url. So, app-routing.module.ts has this for routes:
const routes = [
{ path: ':subdomain', component: TodosComponent },
];
// and the rest of it.
(For simplicity I'm not doing modules here).
So now your TodosComponent simply reads the "subdomain" and fetches stuff from proper backend:
class TodosComponent implements OnInit {
// inject the backend service (or more) and activated route
constructor(private backend: BackendService,
private route: ActivatedRoute) {
}
// on init, get that route param
ngOnInit() {
this.route.params.subscribe(params => {
// catch the "subdomain" param
const subdomain = params.subdomain;
// now you'd move this to a service, but here:
if (subdomain === 'abc1') {
this.backend.get('http://server-1.com/api').subscribe(...);
} else if (subdomain === 'abc2') {
this.backend.get('http://server-2.com/api').subscribe(...);
} else {
// add more as needed here.
}
})
}
}
Now, this would be the same app that would work completely as a SPA as you switch between subdomains.
In the actual app, your app-routing would pass this down to a (lazy) module, this module would have all the components, services etc built so that you can parametrize all the setup with this url segment/route parameter.
I know there are 2 similar questions about this but with no solution for any of them.
So I found this issue in Angular repo where they ask for the same, i.e an alternative for templateCache in Angular 2 but they close it saying you can use CachedResourceLoader.
So my question is how to use this CachedResourceLoader to replace the templateCache, I have been searching on google about this but couldn't find any related content so maybe I am not pointing to the right direction or I missed something.
The answer to this question could be a valid answer for the other 2 similar questions.
Code example for the functionality that templateCache provided in AngularJS:
Adding:
var myApp = angular.module('myApp', []);
myApp.run(function($templateCache) {
$templateCache.put('templateId.html', 'This is the content of the template');
});
Retrieval via $templateCache:
$templateCache.get('templateId.html')
Or retrieval:
myApp.component('myComponent', {
templateUrl: 'templateId.html'
});
CachedResourceLoader is existing yet undocumented Angular 2+ substitution for AngularJS $templateCache:
An implementation of ResourceLoader that uses a template cache to
avoid doing an actual ResourceLoader.
The template cache
needs to be built and loaded into window.$templateCache via a
separate mechanism.
It is supposed to work by providing ResourceLoader provider:
{provide: ResourceLoader, useClass: CachedResourceLoader}
Which was already defined in existing RESOURCE_CACHE_PROVIDER export.
And window.$templateCache is supposed to contain pairs of URLs and responses.
Since ResourceLoader should be specified before compilation, it should be provided not in application module but in compiler options.
Here is an example:
import {RESOURCE_CACHE_PROVIDER} from '#angular/platform-browser-dynamic';
import {COMPILER_OPTIONS} from '#angular/core';
window['$templateCache'] = { 'app.html': `...`};
platformBrowserDynamic({
provide: COMPILER_OPTIONS,
useValue: { providers: [RESOURCE_CACHE_PROVIDER] },
multi: true
}).bootstrapModule(AppModule)
As opposed to AngularJS $templateCache, CachedResourceLoader doesn't allow to make requests for missing templates. This is desirable behaviour most of the time. If it has to be changed, a custom implementation that extends default ResourceLoader implementation can be used instead.
I have built a shared data service that's designed to hold the users login details which can then be used to display the username on the header, but I cant get it to work.
Here's my (abbreviated) code:
// Shared Service
#Injectable()
export class SharedDataService {
// Observable string source
private dataSource = new Subject<any>();
// Observable string stream
data$ = this.dataSource.asObservable();
// Service message commands
insertData(data: Object) {
this.dataSource.next(data)
}
}
...
// Login component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class loginComponent {
constructor(private sharedData: SharedDataService) {}
onLoginSubmit() {
// Login stuff
this.authService.login(loginInfo).subscribe(data => {
this.sharedData.insertData({'name':'TEST'});
}
}
}
...
// Header component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class headerComponent implements OnInit {
greeting: string;
constructor(private sharedData: SharedDataService) {}
ngOnInit() {
this.sharedData.data$.subscribe(data => {
console.log('onInit',data)
this.greeting = data.name
});
}
}
I can add a console log in the service insertData() method which shoes the model being updated, but the OnInit method doesn't reflect the change.
The code I've written is very much inspired by this plunkr which does work, so I am at a loss as to what's wrong.
Before posting here I tried a few other attempts. This one and this one again both work on the demo, but not in my app.
I'm using Angular 2.4.8.
Looking through different tutorials and forum posts all show similar examples of how to get a shared service working, so I guess I am doing something wrong. I'm fairly new to building with Angular 2 coming from an AngularJS background and this is the first thing that has me truly stuck.
Thanks
This seems to be a recurring problem in understanding Angular's dependency injection.
The basic issue is in how you are configuring the providers of your service.
The short version:
Always configure your providers at the NgModule level UNLESS you want a separate instance for a specific component. Only then do you add it to the providers array of the component that you want the separate instance of.
The long version:
Angular's new dependency injection system allows for you to have multiple instances of services if you so which (which is in contrast to AngularJS i.e. Angular 1 which ONLY allowed singletons). If you configure the provider for your service at the NgModule level, you'll get a singleton of your service that is shared by all components/services etc. But, if you configure a component to also have a provider, then that component (and all its subcomponents) will get a different instance of the service that they can all share. This option allows for some powerful options if you so require.
That's the basic model. It, is of course, not quite so simple, but that basic rule of configuring your providers at the NgModule level by default unless you explicitly want a different instance for a specific component will carry you far.
And when you want to dive deeper, check out the official Angular docs
Also note that lazy loading complicates this basic rule as well, so again, check the docs.
EDIT:
So for your specific situation,
#Component({
providers: [SharedDataService] <--- remove this line from both of your components, and add that line to your NgModule configuration instead
})
Add it in #NgModule.providers array of your AppModule:
if you add it in #Component.providers array then you are limiting the scope of SharedDataService instance to that component and its children.
in other words each component has its own injector which means that headerComponentwill make its own instance of SharedDataServiceand loginComponent will make its own instance.
My case is that I forget to configure my imports to add HttpClientModule in #NgModules, it works.