I have an interface for a databaseclient:
export interface IDatabaseClient {
getItem(param: any): Promise<any>
updateItem(param: any): Promise<any>
}
export class DatabaseClient implements IDatabaseClient {
private databaseClient: IDatabaseClient;
constructor(client) {
this.databaseClient = client;
}
public async getItem(item: any): Promise<any> {
return await this.databaseClient.getItem(item);
}
public async updateItem(input: any): Promise<any> {
return await this.databaseClient.updateItem(input);
}
}
Then when I use the dynamodb I just have:
export class DynamoClient implements IDatabaseClient
To create some databaseclient I use it like this:
export class DatabaseClientStrategy {
public static create(): DatabaseClient {
return new DatabaseClient(new DynamoClient(awsSdkClient));
// some other cases here in future
}
}
const client = DatabaseClientStrategy.create();
What would be best way to refactor this? I would like to follow approach for DatabaseClientProvider that would provide different databases and the factory will create a databaseclient. Any ideas how to approach this best ?
Related
I am working on a angular project and my task is to filter a huge file based on the "_type" key which can take different values. Right now I want to first filter _type = "COMPETITION".
My model is defined in a competition.model.ts file which looks like this:
export interface Competition {
product: { active: true };
schemaVersion: number; // 2,
status: string; // PUBLISHED",
comp: string; // "4fc16b10-b8b4-4a99-b9f1-842f0d8b8413",
_createdDate: number; // 1594249198,
discipline: string; // "TRAP [ACTA]",
categories: any; // ["OPEN", "LADIES", "JUNIOR", "VETERAN", "CLAYS_ONLY"],
host: string; // "2",
changeSet: number; // 257,
sync: number; // 155,
seq: number; // 120,
id: string; // "4fc16b10-b8b4-4a99-b9f1-842f0d8b8413",
_type: string; // "COMPETITION",
when: number; // 1597154400,
title: string; // "ACTA Self Nom Test"
}
Here is a my service class where I am trying to implement this:
import { Injectable, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/Operators';
import { Competition } from '../interfaces/competition.model';
#Injectable ({providedIn: 'root'})
export class CompetitionListService {
private loadedCompetitions: Competition[];
private url = '../../assets/data/program1.json';
constructor(private http: HttpClient) {}
public getCompetitions(): Competition[] { return this.loadedCompetitions; }
public fetchCompetition(){
return this.http
.get<Competition[]>(this.url)
.pipe(
map( (responseData) => {
const competitionsArray = [];
for (const key in responseData ) { // responseData is an object
if (responseData.hasOwnProperty(key)) {
// get the_type property
// if ( key.valueOf() === 'COMPETITION') {
competitionsArray.push(
// responseData[key]._createdDate,
responseData[key]._createdDate,
responseData[key]._type,
responseData[key].categories,
responseData[key].changeSet,
responseData[key].comp,
responseData[key].discipline,
responseData[key].host,
responseData[key].id,
responseData[key].product,
responseData[key].schemaVersion,
responseData[key].seq,
responseData[key].status
);
}
}
console.log(competitionsArray);
return competitionsArray;
})
)
.subscribe(competitions => {
console.log(competitions);
this.loadedCompetitions = competitions;
});
}
}
I attached the snapshot of the result on my console, which doesn't really do what I really want to achieve.
I see multiple issues here
You're trying to fetch the asynchronous variable this.loadedCompetitions synchronously. It is not possible. All async variables should be accessed asynchronously. You could use RxJS ReplaySubject multicast observable with buffer 1 to hold and emit the last value.
You don't to manually loop through each item of the array and create a new array with the _type === 'COMPETITION' property. You could use Array filter function to filter out the objects based on a condition.
...
import { Observable, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable ({providedIn: 'root'})
export class CompetitionListService {
private loadedCompetitions: ReplaySubject<Competition[]> = new ReplaySubject<Competition[]>(1);
private url = '../../assets/data/program1.json';
constructor(private http: HttpClient) {
this.fetchCompetition(); // <-- triggers the request and pushes value to `loadedCompetitions`
}
public getCompetitions(): Observable<Competition[]> {
return this.loadedCompetitions.asObservable();
}
public fetchCompetition() { // return nothing here
this.http.get<Competition[]>(this.url).pipe(
map(res => res.filter(item => item['_type'] !== 'COMPETITION'))
).subscribe(
res => this.loadedCompetitions.next(res),
err => console.log(err) // <-- handle error
);
}
}
Now you need to subscribe to loadedCompetitions variable to obtain notifications from it. I've used RxJS takeWhile operator with Subject to close any open subscriptions in the ngOnDestroy hook of the component.
...
import { Observable, Subject } from 'rxjs';
import { takeWhile } from 'rxjs/operators';
export class SomeComponent implements OnInit, OnDestroy {
private close$ = new Subject<any>(); // <-- use to close open subscriptions
constructor(private competitionListService: CompetitionListService) { }
ngOnInit() {
this.competitionListService.loadedCompetitions.pipe(
takeWhile(this.close$)
).subscribe(
res => {
console.log(res);
// other statements that depend on `res`
}
);
}
ngOnDestroy() {
this.close$.next(); // <-- close open subscriptions
}
}
I'm trying to write an API service in a React app, using Axios and Typescript.
This is my code:
interface Service<T> {
GetAll?: Promise<AxiosResponse<T>>;
}
interface Example {
id: Number;
}
const ApiService2 = () => {
const Example = (): Service<Example> => {
const GetAll = (): Promise<AxiosResponse<Example[]>> => {
return axios.get<Example[]>("urlhere");
};
return {
GetAll
};
};
};
And this is my complete error:
Type '() => Promise<AxiosResponse<Example[]>>' is missing the following properties from type 'Promise<AxiosResponse<Example>>': then, catch, [Symbol.toStringTag], finally TS2739
The problem is, you defined you interface Service a bit wrong.
The type of GetAll should not be Promise<AxiosResponse<T>> but a function that returns Promise<AxiosResponse<T>> according to your implementation.
So your Service interface would become:
interface Service<T> {
GetAll?: () => Promise<AxiosResponse<T>>;
}
I have problem with understanding rxjs Observable cooperation with Angular components and their lifecycles (?). I will now describe code pasted below.
I got CartComponent, which holds array of files that have been added to cart.
There is service that provides observables that add or remove files from filesArray (i use checkbox to add/remove). After adding a couple of files to cart, I expected that I would be able to use filesArray in SearchComponent (I used DI). Unfortunately, filesArray is empty in that scope, despite it's content is displayed in cart view properly. I don't understand such behavoir.
Why array is empty and how can I fix that problem? Please help.
CartComponent:
#Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.css']
})
export class CartComponent implements OnInit, OnDestroy {
filesArray: file[] = [];
constructor(private modalService: NgbModal, private cartService: CartService) {
}
open(content) {
this.modalService.open(content);
}
ngOnInit(): void {
this.cartService.addFileToCart$.pipe(
takeUntil(this.componentDestroyed)
).subscribe(file => {
this.filesArray = [...this.filesArray, file];
});
this.cartService.removeFileFromCart$.pipe(
takeUntil(this.componentDestroyed)
).subscribe(file => {
const fileIndex = this.filesArray.indexOf(file);
this.filesArray.splice(fileIndex,1);
});
}
private componentDestroyed: Subject<void> = new Subject();
ngOnDestroy(): void {
this.componentDestroyed.next();
this.componentDestroyed.unsubscribe();
}
}
SearchComponent:
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
constructor(private cartComponent: CartComponent) {
}
private checkOrderedFiles() {
console.log(this.cartComponent.pcFilesArray); //empty
}
CartService:
#Injectable({
providedIn: 'root'
})
export class CartService {
addFileToCart$: Subject<PCFile> = new Subject();
removeFileFromCart$: Subject<PCFile> = new Subject();
constructor() { }
}
Checkbox change event handler - I emit values to subjects here:
addOrRemoveFileFromCart(checkbox, item) {
if(checkbox.checked) {
this.cartService.addFileToCart$.next(item);
} else {
this.cartService.removeFileFromCart$.next(item);
}
}
EDIT:
public readonly files = this.cartService.addFileToCart$.pipe(
scan((filesArray: any, file) => [...filesArray, file], []),
share()
);
template
<div *ngFor="let file of files | async">
{{file.id}}
</div>
Do not use Observables that way in CartComponent. Instead write:
#Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.css']
})
export class CartComponent {
public readonly files = biscan(
this.cartService.addFileToCart$,
this.cartService.removeFileFromCart$,
(filesArray, file) => [filesArray, file],
(filesArray, file) => filesArray.filter(f => f !== file),
[]
),
startWith([]),
);
constructor(private modalService: NgbModal, private cartService: CartService) {
}
open(content) {
this.modalService.open(content);
}
}
In the template use files | async wherever you are currently using fileArray.
In other components, use files as an Observable wherever you are currently using fileArray or pcFileArray.
The underlying issue is that checkOrderedFiles is never invoked — if you write code invoking it, that code never knows when fileArray has changed.
Intuitively, you are trying to "escape" from the Observable, to get your changes back into the static world of the component. That just isn't possible. Once a calculation is in the asynchronous land of Observables, it stays there.
Unfortunately biscan() (a two-Observable variation of scan) is not currently in the library, but you can write it as
const biscan = (leftObs, rightObs, leftFcn, rightFcn, initialValue) =>
new Observable(observer => {
let acc = initialValue;
// this function must be called *twice* (once for left,
// once for right) before the observer is completed.
let complete = () => {
complete = () => observer.complete();
};
const makeSub = (obs, f) => obs.subscribe(v => {
acc = f(acc, v);
observer.next(acc);
},
e => observer.error(e),
() => complete()
);
const leftSub = makeSub(leftObs, leftFcn);
const rightSub = makeSub(rightObs, rightFcn);
return () => {
leftSub.unsubscribe();
rightSub.unsubscribe();
};
});
Edit: fixed typos in biscan()
I resolved that issue with help of Malvolio (wouldn't figure it out by myself for sure). I post working code below, because i suppose that piece posted had some syntax errors. I was having a problem with async pipe, i believe it's because content of CartComponent template is inside modal. I resolved it by manually subscribing.
export class CartComponent implements OnInit, OnDestroy {
filesArray;
private componentDestroyed: Subject<void> = new Subject();
biscan = (leftObs, rightObs, leftFcn, rightFcn, initialValue) =>
new Observable(observer => {
let acc = initialValue;
let complete = () => observer.complete();
const makeSub = (obs, f) => obs.subscribe(v => {
acc = f(acc, v);
observer.next(acc);
},
e => observer.error(e),
() => complete()
);
const leftSub = makeSub(leftObs, leftFcn);
const rightSub = makeSub(rightObs, rightFcn);
return () => {
leftSub.unsubscribe();
rightSub.unsubscribe();
};
});
public readonly files = this.biscan(
this.cartService.addFileToCart$,
this.cartService.removeFileFromCart$,
(filesArray, file) => {
filesArray.push(file);
return filesArray;
},
(filesArray, file) => {
filesArray.splice(filesArray.indexOf(file), 1);
return filesArray;
},
[]
).pipe(takeUntil(this.componentDestroyed));
constructor(private cartService: CartService) {}
ngOnInit(): void {
this.files.subscribe(files => this.filesArray = files);
}
ngOnDestroy(): void {
this.componentDestroyed.next();
this.componentDestroyed.unsubscribe();
}
}
In angular, I get the error: cannot read property "fruits" of undefined. In my class I have:
export class myClass implements OnInit {
fruits: any[];
doSomething() {
this.myArray.forEach(function(m) {
const my = { test: true };
this.fruits.push(my);
}
}
I may have different types that I want to add to my array, so I did not define an interface. What is causing this?
If you want to access this, you should use arrow functions:
fruits: any[] = [];
doSomething() {
this.myArray.forEach(m => {
const my = { test: true };
this.fruits.push(my);
}
}
Arrow functions will cause it to be lexically bounded, allowing you to access this from the object context itself(the 'outer' scope). Read more about it here.
You must initialize the fruits
export class myClass implements OnInit {
public myArray: string[] = ['banana', 'Apple, 'Kumquat']
public fruits: {test: boolean}[] = [];
constructor() {}
ngOnInit() {}
public doSomething(): void {
let my: any = {};
this.myArray.forEach( m => {
my = { test: true };
this.fruits.push(my);
});
}
}
I am trying to use the antlr4 parser in an angular project.
There is a dataservice class in which the invoked function looks like following
parseRule() {
const ruleString = ' STRING TO PARSE';
const inputStream = new ANTLRInputStream(ruleString);
const lexObject = new lexer.scopeLexer(inputStream);
const tokenStream = new CommonTokenStream(lexObject);
const parseObject = new parser.scopeParser(tokenStream);
const result = parseObject.file();
const evaluator = new ScopeEvaluator();
const walker = new ParseTreeWalker();
walker.walk(evaluator, result);
console.log(' result :', result.text);
}
A customer listener is implementing the interface exported with just one method which is enterNonGraphScope.
import { scopeListener } from './antlrscope/scopeListener';
import { NonGraphScopeContext, NamesetSimpleContext } from './antlrscope/scopeParser';
export class ScopeEvaluator implements scopeListener {
constructor() {
console.log('constructed the asdfasf');
}
enterNonGraphScope = function (ctx: NonGraphScopeContext) {
console.log('Tis ', ctx.text);
};
}
Pasting some snippet from the scopeListener.ts for purposes of illustration of the interface
export interface scopeListener extends ParseTreeListener {
/**
* Enter a parse tree produced by the `NonGraphScope`
* labeled alternative in `scopeParser.scope`.
* #param ctx the parse tree
*/
enterNonGraphScope?: (ctx: NonGraphScopeContext) => void;
When I am running the angular ng serve, which is compiling the typescript code.
I am getting an error
ERROR in src/app/rule-parser.service.ts(31,17): error TS2559: Type 'ScopeEvaluator' has no properties in common with type 'ParseTreeListener'.
Below is the listener generated in typescript. (Removed contents and antlr generated comments)
export interface scopeListener extends ParseTreeListener {
enterNonGraphScope?: (ctx: NonGraphScopeContext) => void;
exitNonGraphScope?: (ctx: NonGraphScopeContext) => void;
enterBlockScope?: (ctx: BlockScopeContext) => void;
exitBlockScope?: (ctx: BlockScopeContext) => void;
enterNamesetSimple?: (ctx: NamesetSimpleContext) => void;
exitNamesetSimple?: (ctx: NamesetSimpleContext) => void;
enterGrainExpression?: (ctx: GrainExpressionContext) => void;
exitGrainExpression?: (ctx: GrainExpressionContext) => void;
enterGrainSimple?: (ctx: GrainSimpleContext) => void;
exitGrainSimple?: (ctx: GrainSimpleContext) => void;
enterNamesetExpression?: (ctx: NamesetExpressionContext) => void;
}
It seems to be something to do with typescript interpretation or typing.
I am a newbie to javascript/typescript. Could you please help me out ?
The webpack / generated javascript code functions fine, but this error prevents the build from being generated.
Thanks a lot !
-Vinayak
I did a bit of googling about this and found this to do something with weak type handling change. More details on below link.
https://blogs.msdn.microsoft.com/typescript/2017/06/12/announcing-typescript-2-4-rc/
Based on this an another stackoverflow discussion at
After upgrading TypeScript, Angular controller registration now fails to compile
Below modification to the class implementing interfaces worked.
Option 1 is better as it's just a type assertion.
Option 1
parseRule() {
const ruleString = ' Scope: (Dim1.Attr1 * &Namset1);' +
' Measure.[asdfa] = ddd ;' +
'end scOPe;';
const inputStream = new ANTLRInputStream(ruleString);
const lexObject = new lexer.scopeLexer(inputStream);
const tokenStream = new CommonTokenStream(lexObject);
const parseObject = new parser.scopeParser(tokenStream);
const result = parseObject.file();
const evaluator = new ScopeEvaluator();
const walker = new ParseTreeWalker();
console.log(' type of ' , evaluator);
walker.walk(evaluator as ParseTreeListener, result);
// Parse and execute the code.
console.log(' p :', parseObject);
console.log(' result :', result.text);
}
Option 2 - involves redeclaring stuff ?
import { scopeListener } from './antlrscope/scopeListener';
import { NonGraphScopeContext, NamesetSimpleContext } from './antlrscope/scopeParser';
import { ParseTreeListener } from 'antlr4ts/tree/ParseTreeListener';
export class ScopeEvaluator implements scopeListener {
visitTerminal = () => { };
visitErrorNode = () => { };
enterEveryRule = () => { };
exitEveryRule = () => { };
enterNonGraphScope = function (ctx: NonGraphScopeContext) {
console.log('Tis ', ctx.text);
};
}
If you feel this is not the right way of doing it, please comment. My understanding of typescript is not complete.