I'm trying to create a generic service but not a generic database repository.
I already have a generic repository for database logic. This works.
// entity.repository.ts
export abstract class EntityRepository<T extends Document> {
constructor(protected readonly entityModel: Model<T>) {}
async find(): { }
}
Elsewhere I have a UsersRepository that extends the EntityRepository. This works.
// users.repository.ts
#Injectable()
export class UsersRepository extends EntityRepository<UserDocument> {
constructor(#InjectModel(User.name) userModel: Model<UserDocument>) {
super(userModel)
}
}
I want to create a generic service that can take in different instances of the entity repository, so it can be a UsersRepository, CatRepository or DogRepository so it's defined generically here. I'm having trouble defining the type for this generic repository. The actual injected repository here will be an instance of entityRepository - for example, the UsersRepository above should be able to be injected in this EntityService.
// entity.service.ts
#Injectable()
export abstract class EntityService<T extends Document> {
constructor(
protected readonly entityRepostiory: EntityRepository<T>,
private readonly myOtherService1: MyOtherService1,
private readonly MyOtherService2: MyOtherService2,
)
public myMethod() {
// Property 'find' does not exist on type 'EntityRepository<T>'
const user = this.entityRepository.find({}); // <- Type error
}
}
And then I want to create an instance of the above base EntityService. Not sure how to type this out properly either
// cat.service.ts
#Injectable()
export class CatService extends EntityService<CatDocument> { // <- not sure about typing
constructor(
private readonly catRepository: CatRepository<CatDocument>, // <- ???
private readonly myOtherService1: MyOtherService1,
private readonly MyOtherService2: MyOtherService2,
) {
super(catRepository, myOtherService1, myOtherService2) // <- I need to call super on all 3 right?
}
}
Related
I am trying to add a public or private variable in the class definition on calling a public method of a class for example in the class definition given below.
export default class Container {
public addFlag(flagName: string): void {
// create a new private or public property in class and assign a value to it.
}
}
If I make an instance of container class and call addFlag() on that instance, I want to add a public or private variable in the class definition and assign a value to it, how can i achieve this in Typescript ?.
Make your class become dynamic object with [index signature][1]
class Container {
[key: string]: any;
public addFlag(flagName: string): void {
this.flagName = flagName;
}
}
const container = new Container();
container.addFlag("abc");
console.log(container.flagName); // "abc"
But index signature can't use Access modifiers: public, private or protected.
I am trying to learn strict typing in Typescript.
I defined these classes:
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
private param: TParam;
setInitParams(init: TParam): void {
...
}
getInitParams(): TParam {
....
}
}
export class MyClass extends MyAbstractClass<AParamType> {
private param: AParamType;
...
}
The problem is that I get the error " Class 'MyClass' incorrectly extends base class 'MyAbstractClass'.
Types have separate declarations of a private property 'param'."
I don't understand why I get this error because AParamType type correctly extends MyParamBaseType
Can somebody helps me ? Thanks for your help.
The private keyword is just a compile time checked. This means that the field param will be stored at runtime in the instance of the class. MyAbstractClass declares it's member private, so if MyClass were allowed to redeclare the param field it would end up accessing the same field named param in the instance at runtime breaking privacy.
You can use the ES private class fields (with #). These ensure hard privacy even at runtime and name collisions in the sub class are not an issue since each declared field is distinct even if they share the same name:
type MyParamBaseType = {}
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
#param!: TParam;
setInitParams(init: TParam): void {
}
getInitParams(): TParam {
return this.#param;
}
}
type AParamType = {}
export class MyClass extends MyAbstractClass<AParamType> {
#param!: AParamType;
}
Playground Link
Or if you want to access the same field from the base class you might consider protected instead:
type MyParamBaseType = {}
export abstract class MyAbstractClass<TParam extends MyParamBaseType> {
protected param!: TParam;
setInitParams(init: TParam): void {
}
getInitParams(): TParam {
return this.param
}
}
type AParamType = {}
export class MyClass extends MyAbstractClass<AParamType> {
protected param!: AParamType;
}
Playground Link
Within my anguular app , i ve this service :
#Injectable()
export class myService{
myBehaviouSubject= new BehaviorSubject("");
setData(){
this.myBehaviouSubject.next("123");
}
}
Inside my app.component , i m able to get the value , but i want to keep it readonly or editable only inside the service itself , i want to prevent to push any data from component (.next('DATA'))
#Component({
})
export class AppComponent implements OnInit {
constructor(public myService : MyService) { }
getData(){
// GET VALUE
this.myService.myBehaviouSubject.value
}
unwantedMethodToSetValue(){
// SET VALUE -> i want to prevent this
this.myService.myBehaviouSubject.next("unwanted value")
}
}
Suggestions ?
You can keep the observable inside service only by declaring it as private field of a class.
#Injectable()
export class myService {
private myBehaviouSubject = new BehaviorSubject("");
// Use this observable inside the app component class.
myBehaviouSubjectObservable = myBehaviouSubject.asObservable();
setData() {
this.myBehaviouSubject.next("123");
}
}
#Component({
})
export class AppComponent implements OnInit {
constructor(public myService: MyService) {}
getData() {
// You can subscribe to observable and can get value here
this.myService.myBehaviouSubjectObservable.subscribe((value) => {
console.log(value);
})
}
unwantedMethodToSetValue() {
// SET VALUE -> you cannot do this here now.
this.myService.myBehaviouSubject.next("unwanted value")
}
}
Use property access modifiers:
#Injectable()
export class MyService{
private myValueSubject: BehaviorSubject<string> = new BehaviorSubject<string>("");
public readonly myValueObservable: Observable<string> = this.myValueSubject.asObservable();
public setData() {
this.myValueSubject.next("123");
}
public getData(): string {
return this.myValueSubject.value;
}
}
Instances of MyService will not have a publicly accessible subject.
I usually try to avoid a method like getData, favoring subscriptions to the related observable. If I ever find myself writing those kinds of methods, it's a warning flag to re-evaluate my architecture. If you just want to store a value and get/set it with methods, use a plain old private property. The entire purpose of the subject is defeated if you are only ever getting the value through a method like getData()
Check out the documentation for typescript classes, which discusses access modifiers: https://www.typescriptlang.org/docs/handbook/classes.html
The traditional answer : If you return the Subject as an observable, you disallow .next() calls.
But in your case, you also want direct access to the current value without subscribing, so you could add a getter for that too.
#Injectable()
export class myService{
private readonly myBehaviouSubject = new BehaviorSubject("");
setData(){
this.myBehaviouSubject.next("123");
}
public get myObservable$(): Observable<string>{
return this.myBehaviourSubject;
}
public get currentValue(): string{
return this.myBehaviourSubject.value;
}
}
https://stackblitz.com/edit/angular-protected-rxjs-subject
in this solution which I hope meet you needs:
be aware that there is no subscription
fetching updates handled manually
Property 'myBehaviourSubject' is private and only accessible
within class 'TestService'.
I'm trying to create a generic repository for every entity in the application
mongo-repository.ts
import { Document, Model, Types } from 'mongoose';
type MongooseModel<T> = Model<T & Document>;
export abstract class MongoRepository<T extends MongooseModel<T>> {
protected constructor(
protected readonly model: T,
) {}
}
user.repository.ts
import { User } from '../../../models/User';
import { MongoRepository } from '../../common/mongo/mongo-repository';
class Repository extends MongoRepository<User> {
constructor() {
super(User);
}
}
export const UserRepository = new Repository();
Actual results:
src/modules/user/repository/user.repository.ts:4:42 - error TS2304: Cannot find name 'User'.
4 class Repository extends MongoRepository<User> {
~~~~
Expected result:
Work.
However, I'm not getting this error message in super(User) but only in the generic declaration
Fixed,
Fix:
MongoRepository<T extends typeof Model>
class Repository extends MongoRepository<typeof User>
I want to isolate http interactions by creating data access objects from a class so that in a component I might simply get data like this:
// dashboard.component
import { AppUser } from './appuser.service'
export class DashboardComponent implements OnInit {
user: AppUser = new AppUser();
constructor() { }
ngOnInit() {
let id = JSON.parse(window.localStorage.getItem('session')).userId;
this.user.find(id) // 'find' is from base class
.subscribe(
// handle user data
);
}
}
I have defined a base class and a sub class like this:
// base-resource.service
import { HttpClient } from '#angular/common/http';
...
export class BaseResource {
private fullpath: string;
protected http: HttpClient;
constructor (path: string) {
this.fullpath = path;
}
find (id): Observable<Object> {
return this.http.get(this.fullpath + '/' + id); // this line throws Error!
}
}
// app-user.service
...
export class AppUser extends BaseResource {
constructor(data?) {
super('api/appusers');
}
}
However this generates an error: ERROR TypeError: Cannot read property 'get' of undefined from within the base class function.
My 'AppUser' instance is clearly inheriting find from 'BaseResource', but find is picking up the 'AppUser' instance as the value of this and http is not available. I have tried declaring http as public and private as well as protected, but that had no effect. I imagine I'm missing some bigger picture of how to extend classes.
As specifically as possible, i think my question is in how to abstract functions to a base class when they need access to the base class's context.
(using Angular 6.0.4)
EDIT
I updated the title as it became clear that this is a problem of instantiating the HttpClient service in a class.
The error is because nothing is instantiating HttpClient, so it is undefined when you come to use it.
You should inject HttpClient into AppUser, and pass it into BaseResource via the constructor
export class AppUser extends BaseResource {
constructor(HttpClient http) {
super(http, 'api/appusers');
}
}
And in base-resource.service
import { HttpClient } from '#angular/common/http';
...
export class BaseResource {
private fullpath: string;
protected http: HttpClient;
constructor (httpClient: HttpClient, path: string) {
this.fullpath = path;
this.http = httpClient;
}
find (id): Observable<Object> {
return this.http.get(this.fullpath + '/' + id); // this line throws Error!
}
}