lb4 call another repository in different repository - javascript

What is the best way of calling different repository at the main repository? I tried to use Services. For example
...
#injectable({scope: BindingScope.TRANSIENT})
export class ProjectService {
constructor(#repository(ProjectRepository) public projectRepository: ProjectRepository) { }
}
Here is my service code. When I define this service at another repoository, I am able to access projectRepository. But I am not sure this is the best way.

We can take a page from Relations [1] and utilize #repository.getter() [2] to perform dependency injection in the main Repository:
import {Getter} from '#loopback/core';
import {DefaultCrudRepository, repository} from '#loopback/repository';
export class MainRepository() extends DefaultCrudRepository</*...*/>{
constructor(
#repository.getter(ProjectRepository)
private _projectRepositoryGetter: Getter<ProjectRepository>;
)
async yourFunction(): Promise<void> {
let projectRepository = await this._projectRepositoryGetter();
// `projectRepository` now contains an instance of ProjectRepository.
}
}
In the example above, we're utilizing constructor injection and retrieving the ProjectRepository instance in an arbitrary function called yourFunction. However, you can use any flavour of dependency injection [3] and retrieve the instance in any function within the same closure.
Why getters?
Getters to prevent circular dependencies. These are especially prevalent in Repositories as they may have Relations that interconnect between each other. Getters achieve this by delaying the binding resolution until explicitly requested by the LB4 application.
When to use Services?
Services can be considered a higher-level abstract than Repositories. They aren't expected to implement a standard CRUD or KV interface and aren't assumed to be linked to a DataSource. This is useful if you need logic that are out-of-scope of Repositories. For example, a Service may implement a function that adds a new database record and uploads a file to Amazon S3. This keeps a clear separation between the code that interfaces with the Datastore and those that don't, thereby ensuring that the Datastore logic is kept predictable, readable and easy to test.
Links
[1] https://loopback.io/doc/en/lb4/HasMany-relation.html#configuring-a-hasmany-relation (archive)
[2] https://loopback.io/doc/en/lb4/apidocs.repository.repository.getter.html (git permalink)
[3] https://loopback.io/doc/en/lb4/Dependency-injection.html#flavors-of-dependency-injection (git permalink)

Related

Nestjs how to clear ( reset ) all cache

I'm building an API and the data get updated every day at 3 am and need to clear all cached endpoints whatever is!
I'm using the CacheModule and the decorator #UserInterceptor(CacheInterceptor) to cache whatever I need in the controller.
there a Cron function that runs every day at 3 am to update the content, I need to know what the code should put in that method to clear all cache.
You can inject the underlying cache manager instance:
constructor(#Inject(CACHE_MANAGER) protected readonly cacheManager) {}
And then use it to delete the caches for all keys:
const keys = await this.cacheManager.keys()
await this.cacheManager.del(keys)
According to the official NestJs docs (July 2022), they offer a .reset() method to "clear the entire cache". This examples assumes you're using the naming convention in their docs, where cacheManager is the locally scoped & injected CACHE_MANAGER from #nestjs/common and "Cache" from cache-manager package.
// inside the class constructor
constructor(#Inject(CACHE_MANAGER) private cacheManager: Cache) {}
// and then inside a class method
await this.cacheManager.reset();
Reference: https://docs.nestjs.com/techniques/caching

Loopback4 Error in dependency injection: is not bound to any value in context application

i have this error
Unhandled error in POST /client: 500 Error: The key 'controllers.pointController' is not bound to any value in context application
My class
constructor(
#repository(ClientRepository)
public clientRepository: ClientRepository,
// Controllers
#inject('controllers.pointController')
public pointController: PointController,
) {
}
// functions
i follow the documentarion of loopback4 and dependency injection, but doesn't work
any idea?
By default, LoopBack uses PascalCase keys when binding controller classes, see e.g. this test:
https://github.com/strongloop/loopback-next/blob/0444120cda7119c66bc2170f4817e67d8dc9d312/packages/core/src/tests/unit/application.unit.ts#L25-L33
expect(binding.key).to.equal('controllers.MyController');
Your example does not provide enough information, so I'll assume your controller is defined as a class PointController in src/controllers/point.controller.ts file and you are using #loopback/boot to load and register your application's artifacts.
In that case, you need to fix your code as follows - notice the upper-case P:
#inject('controllers.PointController')
Additional information
In the future, you can use debug logs to find binding keys created for the different artifacts. On Unix (MacOS, Linux):
DEBUG=loopback:context:binding npm start
In the debug log, you should see a message like this:
loopback:context:binding Bind controllers.PointController to class PointController
The part controllers.PointController is the binding key to use for #inject, the part PointController is the name of the controller class.

Best practice/only possibility. "Json to Javascript / Typescript Object by constructor"

I started developing with angular / typescript and created a service for my .NET Core API and I like to know what's the best way to get a clean and reliable object from my service.
I have an .NET CORE REST API returning a json result represented by the class definition below.
Service:
demoservice.getDemo().subscribe((val) => new Demo(val));
Demo-Class is the following code:
export class Demo {
public id : number;
public name : string;
public subDemo: SubDemo;
constructor(demo: Demo) {
this.id = demo.id;
this.name = demo.name;
this.subDemo = new SubDemo(demo.subDemo);
}
}
export class SubDemo {
public demoList : ListDemo[]
constructor(subDemo: SubDemo) {
this.demoList = new Array<ListDemo>();
subDemo.demoList.forEach(dl => {
this.demoList.push(new ListDemo(dl))
});
}
}
export class ListDemo {
constructor(listdemo : ListDemo) {
this.birthday = listdemo.birthday;
this.smoker = listdemo.smoker;
}
get birthDayFormatted() : Date {
return new Date(this.birthday);
}
public birthday : string;
public smoker : boolean;
}
I this the best way (full implement all constructors) to create a object. Please note I like to use the "getter" - functionality of my ListDemo Class.
Is there no better way? I just found some Object.clone / Object.assign / Object.create.
But none of this solution is comprehensive...
I am really interested in your experience..
Since you're using better & best I will answer with my, probably unwanted, opinion. Disclaimer: I'm no guru, this answer is based on opinion, feel free to disregard.
Don't do it. Your server has a set of objects in its domain, probably some kind of problem solving or data storage domain.
Your client has a set of objects in its domain, typically focused on presenting the data to the user and allowing the user to understand and manipulate it.
Both of these domains may have objects that have the same name or are based on the same real world concept. It can be tempting to feel like they are the same domain with the same objects. They are not. If they were the same you would not be writing a client and a server you would be writing two of the same thing. The two should communicate with pure data objects. In TS this means you should only create an interface, not a class, for the objects you receive from the server.
Instead, start over. Create a new domain based on what you want to appear in the API. If you design your API from the top (UI) down to the bottom (access services) you'll likely find that you don't have a one-to-one mapping between objects. On the occasions you do then you can probably get away with the occasional assign / merge (see below) but I've personally never found reason to do more than what you posted above.
Should you persist you may come across the option to reassign the prototype of the JSON literal from the base object to the class the data represents but that is a contentious topic and potential performance pitfall.. Your best bet is probably to just do a recursive/deep assign like Lodash's merge.
Just use interfaces not classes.
export interface Demo {
id: number;
name: string;
subDemo: SubDemo;
}
export interface SubDemo {
demoList: ListDemo[];
}
export interface ListDemo {
birthday: string;
smoker: boolean;
}
and your api should return the same shape, you should just be able to go
getDemo(): Observable<Demo> {
return this.http.get<Demo>('url');
}
in your service and in your component assign it to a property
demo$ = this.service.getDemo();
and then use the observable with the async pipe in your template.
<ng-container *ngIf="demo$ | async as demo">
{{ demo | json }}
</ng-container>
The less you have to manipulate your data the better. I have a VS pluging that allows you to paste C# classes into TS files and it converts them to TypeScript interfaces on the fly.

Angular 2, How to Share Array Data Between components using service?

In Angular2, Share Array data between components using service?
I'm designed this structure like below.
object structure
{
data: 'data'
keys: ['key1', 'key2', ... , 'keyN']
}
* Service has a array of total objects.
totalObject = [object1, object2, ... , objectN]
At first I initialized selectedObject of service like this.
selectedObject = totalObject;
Then I initialized selectedObject of Component B on constructor like this.
constructor(private wordService: WordService) {
this.words = wordService.selectedWords;
}
At the first, Component B displayed All objects correctly!!
But, when the service initialize new array to selectedObject, Component B cannot display selected objects.
// It's not working...
// remove
this.selectedWords.splice(0, this.selectedWords.length);
// add
for(let i in WORDS) {
if(WORDS[i].keys.indexOf(key) >= 0) {
this.selectedWords.push(WORDS[i]);
}
}
You can simply create a service and use it as a "singleton" service.
#Injectable()
export class DataService {
public selectedWords:string[] = [];
}
And you provide it at the top level of your application, this way only one instance will be used across your app:
#NgModule({
imports: [ BrowserModule ],
declarations: [ App, OtherComponent ],
bootstrap: [ App ],
providers: [ DataService ]
})
export class AppModule {}
Plunkr example
If I understand what you're trying to do, you are trying to manipulate an object, by reference, across two components, with a service as sort of a broker, such that changes by component A to Object X will be visible in component B. The service more or less acts as just a place to stash references.
You will achieve a lot more stability, make it easier to debug, and make it a lot more extensible, thinking this way:
Component A makes change to Object X (which it houses itself).
Component A updates model in Service (which as several people here say, acts as a singleton, or more technically correct, a "managed instance"), with a copy of Object X. The model/service now has the data, but that data has no external reference that can accidentally (or otherwise) mutate it.
When necessary, Service dispatches a "dirty data" notification, which Component BCDE...etc. is listening for. This notification contains the new data (this is "push").
Component BCDE...etc. uses that data. It is not reliant on a reference outside of it's control concern and it is not tightly coupled to that service.
Any other component that needs to modify data used by other components, just uses the same mechanism.
Any other component that wants to get the data on demand from the service, can grab a copy of it from a getter on that service (this is "pull").
I have tried to do what you're doing (pretty sure we all have). Over time it's just trouble (especially if you throw more components into the mix). Using notifications/events is more staightforward all around, even if it might seem more complex to initially set up. It's easier to test since you just test with the payload from a notification/event (easily triggered in a test), you don't have to set up the other component and have it modify the service/reference used in the target component.
But yeah, the whole "one reference on a singleton everything is looking at" thing is just trouble.

Dependency Injection in a distributable Javascript library?

We are using Backbone to create reusable components. We create controllers in order to setup bindings between models and views. We wish to offer people the ability to replace the models and views with their own implementations.
Since the majority of people will use the the components we provide, I don't want to force developers to configure or create anything that isn't different from the defaults.
This means that someone should be able to pass an instance of an object they want to use as a model or view to the controller, all configured and setup and ready to go, or they can pass in some configuration to what the controller will use by default.
I am not sure what the best approach is.
// Idea #1
var controller = new Controller({
dependencyA: {
conf: { // config for depedencyA }
},
dependencyB: {
conf: { // config for dependencyB }
class: custom.implement.Class
}
});
In this approach, the user doesn't have control over how to instantiate the object. What's bad about this is, for example, Backbone models take two arguments in the constructor while views only take one.
// Idea #2
var controller = new Controller({
dependencyA: {
args: ['arg1',{
opt1: 'opt-value',
}]
},
dependencyB: {
args: ['a','b','c']
class: custom.implement.Class
}
});
Args would be the arguments passed to a constructor. This means the controller calls the constructor with the args array, and again this only really benefits you if you're passing in custom configuration for default dependencies. If you want to pass your own implementation it's more awkward.
// Idea #3
var controller = new Controller({
dependencyA: new outOfBoxModel({ // configuration }),
dependencyB: new custom.imeplement.Class('a','b','c')
});
In this approach, the user is forced to instantiate the out of box model. If the model's default settings are all appropriate though, then the user is doing unnecessary work. The only bit they HAVE to do is create an instance of their own custom implementation.
I am not sure what the best approach would be here?
Of the three approaches, I most prefer approach number 3. Here is why:
It is more consistent than the other approaches. In the 3rd approach, the user only has to learn to pass in constructed instances of dependencies into the controller. In the other approaches, the user has to pass in either args, or args and a class name.
It does not violate the Single Responsibility Principle. In the first two approaches, your controller is made responsible for constructing and configuring its dependencies. This doesn't feel like dependency injection at all! I think it's better, and simpler, to leave the construction of dependencies to the user or another part of your application. In my opinion, its not a terrible thing to force the user to construct their own implementations - it gives them the freedom to define their constructors however they want, rather than forcing you to define and maintain constructor APIs for the Controllers dependencies, and forcing the user to conform to them.
A different idea:
If you have this freedom in your application, I would consider putting your Controller construction logic in a factory class or method:
var createController = function(config) {
// Parse, validate, extract relevant config items
// var a = create dependency a
// var b = create dependency b
return new Controller(a, b);
}
This approach allows you to be as fancy as you want with your definition of config - you could support all three of the config definitions you provided in your original post - although I wouldn't recommend that :-). At a minimum, I would have the factory method support a zero args invocation (in which case it would return the default construction of Controller) and one of your preferred config definitions.

Categories

Resources