Angular2 Service call Model Method - javascript

I'm new to angular2 so I will try to make this question as clear as possible. I want to have a method in my model that I can call from my service. Right now I have it trying to replace the name.
Here is my model
export class Secret {
public name: string;
constructor (
public id: number,
public type: string,
public visible_data: any,
public secrets?: any,
public group_id?: number,
public group_name?: string
) {
this.name = this.myName();
}
public myName(): string {
return this.name = "whaddup"
}
}
and my service method
/*
* get secrets
*/
public getSecrets(): Promise<Secret[]> {
let tempArray = [];
return this.sdk.list_secrets()
.then((resp) => {
resp.map((item) => {
tempArray.push({
id: item.id,
name: this.secret.myName(),
type: item.type,
visible_data: {
"username": item.data
}
}); // end push
});
return tempArray;
})
.catch((err) => {
console.error(err);
});
}
list.component.ts code:
export class ListComponent implements OnInit {
public constantArray: Secret[];
private secrets: Secret[];
private secret: Secret;
constructor(private secretService: SecretService) { }
public ngOnInit() {
this.getSecrets();
}
public getSecrets() {
this.secretService.getSecrets()
.then((data) => {
this.secrets = data;
this.constantArray = data;
});
}
}

Instead of
tempArray.push({
id: item.id,
name: this.secret.myName(),
type: item.type,
visible_data: {
"username": item.data
}); // end push
You should do
tempArray.push(new Secret(item.id, ...));
Reason: in your original code, the object pushed to array are plain old javascript object and they are not typescript objects. The 'new ...' will create a real typescript class object.

To work with angular2 service, you need to do two things. First of all you need to at the #Injectable annotation on top of your services.
If you want to inject services into each other, you need to provide them on your NgModule like this:
#NgModule({
selector: 'my-app',
providers: [NameService]
}):
If you want to inject the service into another service/component you can just leverage typescript and use constructor injection like this:
constructor(private secretService: SecretService) {}

Related

How to display data from client service in ngx-wheel Angular

I want to display the ngx-wheel using api but I'm having trouble displaying the data.
Here my Service :
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class RestServices {
restEndpoint:string = 'https://gorest.co.in/public/v2/users'
constructor(
private httpClient: HttpClient
) { }
async getServiceId() {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
}
return this.httpClient.get<any[]>(this.restEndpoint, httpOptions)
}
Here my Component :
private subscription: Subscription | undefined;
items: any = []
ngOnInit(): void {
this.subscription = this._restService.getServices()
.subscribe((res:any)=>{
let item = res
this.items = item.map((v:any) => ({
text: v.name,
id: v.id,
textFillStyle: "white",
textFontSize: "16"
}));
})
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
Here for html
<ngx-wheel #wheel [width]='350' [height]='350' [spinDuration]='8' [disableSpinOnClick]='true' [items]='items'
[innerRadius]='10' [spinAmount]='10' [textOrientation]='textOrientation' [textAlignment]='textAlignment'
pointerStrokeColor='black' pointerFillColor='white' [idToLandOn]='idToLandOn' (onSpinStart)='before()'
(onSpinComplete)='after()'>
I hope to find the answer here. Thank you
First, you don't need await, async and ,toPromise()... remove them and simply return
return this.httpClient.get<any[]>(this.restEndpoint, httpOptions);
inside your component you should use your constructor only for simple data initialization: if you have to consume a rest api it is a better approach to move that piece of code inside the ngOnInit method:
items: any[] = []
constructor(private restService: RestService){}//dependency injection
ngOnInit(): void {
this.restService.getServiceId().subscribe(response => {
console.log('response success: ', response);
this.items = response; //this may change a little based on your api
console.log('items: ', this.items);
}, errorLog => {
console.log('response error: ', errorLog)
});
}
The above solution is valid, you can enrich it by adding a *ngIf="isLoaded" on your html element and set to true the isLoaded INSIDE subscribe method. but if you prefer you can do the following in the component.ts
items$: Observable<any> = EMPTY;
constructor(private restService: RestService){}//dependency injection
ngOnInit(): void {
this.items$ = this.restService.getServiceId();
}
then, in your html it would change to the following:
<ngx-wheel #wheel *ngIf="items$ | async as items" [width]='350' [height]='350' [spinDuration]='8' [disableSpinOnClick]='true' [items]='items'
[innerRadius]='10' [spinAmount]='10' [textOrientation]='textOrientation' [textAlignment]='textAlignment'
pointerStrokeColor='black' pointerFillColor='white' [idToLandOn]='idToLandOn' (onSpinStart)='before()'
(onSpinComplete)='after()'>

Chaining HTTP call in Angular

I'm trying to get Google Places with their API and get a response body that contains a "photo_reference" property. With this property, I want to get the Google Place Image and call a second API.
I have an input field that can search for places. By entering a string and clicking on the search button, this method will get called:
#Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {
private lng: number;
private lat: number;
private place_id: string;
private listOfPoi: GoogleLocation[];
constructor(
private gs: GoogleService,
private ds: DataService,
private router: Router
) { }
ngOnInit(): void {
this.ds.getLocation().subscribe(loc => this.listOfPoi = loc);
}
searchPoi(location: string): void {
this.router.navigate(['/poi']);
this.gs.getPoi(location).pipe(
map((response) => response)
).subscribe((data: GoogleResponse) => {
if(data.status === "OK") {
this.ds.setLocation(data.results); //data.results is an array of locations
}
});
}
In my other component, I try to get the locations and map them into a new object with the property imgUrl.
I want to save them in an array.
export class SightseeingViewComponent implements OnInit {
listOfPoi: LocationWithImage[];
constructor(private ds: DataService, private gs: GoogleService) {}
ngOnInit(): void {
this.ds.getLocation().subscribe(
(loc) =>
(this.listOfPoi = loc.map((loc) => {
return {
...loc,
imgUrl:
this.gs.getImage(loc.photos[0].photo_reference).toString(),
};
}))
);
}
}
But my problem is the img src="[object Object]" it should be the string!
My problem is this line this.gs.getImage(loc.photos[0].photo_reference).toString()
I tried to replace this line with a random hardcoded img Url and it works. The image are shown but I cannot retrieve the URL as string with this method.
Of course toString() is not working but I have no clue what else I can do?
This is the google location model:
//The model of a google location object from the API response (getLocationInfo)
export class GoogleResponse {
constructor(
public results: GoogleLocation[],
public status: string
) {}
}
export class GoogleLocation {
constructor(
public formatted_address: string,
public geometry: Locations,
public icon: string,
public name: string,
public photos: PhotoInfo[],
public place_id: string,
public reference: string,
public type: string[]
) {}
}
export interface LocationWithImage extends GoogleLocation {
imgUrl?: string;
}
...
EDIT:
This is my service:
With this, I make a call to my backend which is fetching the photo_reference from Google via its API
#Injectable({
providedIn: 'root',
})
export class GoogleService {
url: string = 'http://localhost:3000/';
constructor(private http: HttpClient) {}
getLocationInfo(location: string): Observable<GoogleResponse> {
console.log(location);
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
return this.http.post<GoogleResponse>(
this.url + 'googleplace/',
{ name: location },
{ headers: headers }
);
}
getPoi(location: string): Observable<GoogleResponse> {
console.log(location);
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
return this.http.post<GoogleResponse>(
this.url + 'googlepoi/',
{ name: location },
{ headers: headers }
);
}
getImage(photoRef: string): Observable<string> {
console.log(photoRef);
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});
return this.http.post<string>(
this.url + 'googlephoto/',
{ photoRef: photoRef },
{ headers: headers }
);
}
}
Here is my other service which is needed to send and retrieve data from other components
#Injectable({
providedIn: 'root'
})
export class DataService {
private googleLocationSource = new Subject<GoogleLocation[]>();
constructor() { }
public getLocation(): Observable<GoogleLocation[]> {
return this.googleLocationSource.asObservable();
}
public setLocation(gl: GoogleLocation[]) {
return this.googleLocationSource.next(gl);
}
My problem is this line this.gs.getImage(loc.photos[0].photo_reference).toString()
Yes, calling toString() on an observable isn't going to work ! :-)
You need to subscribe to that observable to receive its result. There are a few "Higher Order Mapping Operators" that can do this for you, so you don't have to deal with nested subscriptions. In this case, we can use switchMap.
However, it's a little more complex because you want to make a call for each element in the returned array. We can map each location to an observable call to get the image and use forkJoin to create a single observable that emits an array containing the results of the individual observables:
this.ds.getLocation().pipe(
switchMap(locations => forkJoin(
locations.map(loc => this.gs.getImage(loc.photos[0].photo_reference))
)
.pipe(
map(imgUrls => imgUrls.map(
(imgUrl, i) => ({ ...locations[i], imgUrl })
))
))
)
.subscribe(
locations => this.listOfPoi = locations
);
The flow here is:
switchMap receives the locations and subscribes to observable created by forkJoin
forkJoin creates an observable that will emit an array of the results (image urls) from all the individual getImage() calls.
map receives the array of image urls and maps it to an array of locations that include the image url
subscribe simply receives the final array

Is there a way to get the details of a class in a typescipt program so that I can pass these details to a logger(winston)?

I have written a wrapper over winston.In order to test this I have also written a "Greeting class" wherein I make a call to logger.info().I want my log to also display the class name.
For this when I invoke logger.info() I also have to pass the name of the class so that my wrapper knows which class the logger was invoked at.But the problem is that I do not want the programmer to specify that.I want to create a level of abstraction.My wrapper should be user friendly.
Is there a way to do this?
If the above mentioned problem is not solvable is there a way to get all details of a class and its methods.I tried passing "this" but im getting something like {greeting:world}
Sorry if im not using correct terminology.I am new to javascript and node.js.
wrapper:
import "reflect-metadata";
import { injectable } from "inversify";
import { createLogger, format, transports, Logger as Iwinston } from "winston";
const { combine, timestamp, metadata, json, errors, label, printf } = format;
#injectable()
export default class Logger {
public logger: Iwinston;
private selfInfo: string;
constructor() {
this.selfInfo = "winston version >= 3.x";
this.logger = createLogger(this.readOptions());
this.addTransportConsole(this.getTransportConsoleOptions());
}
public info(message: string, data?: any): void {
console.log(data);
this.logger.info(message,data);
}
public error(message: string, data?: any): void {
this.logger.error(message, data);
}
public getCoreVersion(): string {
return this.selfInfo;
}
private readOptions(): Object {
return {
format: combine(
label({ label: process.env.APP_NAME || "Unknown App" }),
errors({ stack: true }),
timestamp(),
json(),
),
transports:[
new transports.File({filename:'error.log',level:'info'})
]
};
}
private getTransportConsoleOptions(): transports.ConsoleTransportOptions {
return {
debugStdout: false
};
}
private addTransportConsole(
options?: transports.ConsoleTransportOptions
): void {
this.logger.add(new transports.Console(options));
}
}
My test:
import Logger from '../lib/component/logger/appLogger/winstonLogger'
let logger:Logger
logger = new Logger()
test('Winston Logger Test',() => {
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
logger.info('hello',this)
}
greet() {
console.log(globalThis.Document)
logger.info('in greet')
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
greeter.greet()
})
//{"level":"info","message":"hello","metadata":{},"label":"Unknown App","timestamp":"2019-11-22T09:30:07.256Z"}
So what i would do here is make them pass the class as a parameter when you create the instance of the logger:
const logger = new Logger<Greeter>(Greeter)
test('Winston Logger Test',() => {
class Greeter {
....
This then means this logger instance is linked to this class now for the whole time it is used. In your logger class you can then in the constructor set the name of that class which i have abstracted away in nameByInstance method.
import "reflect-metadata";
import { injectable } from "inversify";
import { createLogger, format, transports, Logger as Iwinston } from "winston";
const { combine, timestamp, metadata, json, errors, label, printf } = format;
#injectable()
export default class Logger<TInstance> {
public logger: Iwinston;
private selfInfo: string;
private loggerInstanceName: string;
constructor(instance: TInstance) {
this.selfInfo = "winston version >= 3.x";
this.logger = createLogger(this.readOptions());
this.addTransportConsole(this.getTransportConsoleOptions());
this.loggerInstanceName = this.nameByInstance(instance);
console.log(this.loggerInstanceName) // Greeter :)
}
public info(message: string, data?: any): void {
console.log(data);
this.logger.info(message,data);
}
public error(message: string, data?: any): void {
this.logger.error(message, data);
}
public getCoreVersion(): string {
return this.selfInfo;
}
private readOptions(): Object {
return {
format: combine(
label({ label: process.env.APP_NAME || "Unknown App" }),
errors({ stack: true }),
timestamp(),
json(),
),
transports:[
new transports.File({filename:'error.log',level:'info'})
]
};
}
private getTransportConsoleOptions(): transports.ConsoleTransportOptions {
return {
debugStdout: false
};
}
private addTransportConsole(
options?: transports.ConsoleTransportOptions
): void {
this.logger.add(new transports.Console(options));
}
private nameByInstance(type: TInstance): string {
return type.prototype["constructor"]["name"];
}
}
This now means you can just let them do logger.info('hello') and you have context to the class name in the logger instance loggerInstanceName to add that to the message they have passed in within the info method in logger without them having to do it.. nice hey?!

Angular5 - Error: Function DocumentReference.set() called with invalid data. Data must be an object, but it was: a custom PlatformModel object

I find allready some posts on google where people solve this problem. but i cant reproduce the solutions on my project.
My Interface:
declare module PlatformInterface {
export interface Design {
primaryColor: string;
backgroundImage: string;
}
export interface Saga {
id: string;
name: string;
short_desc: string;
desc: string;
manga: Manga[];
anime: Anime[];
}
export interface Root {
id: string;
name: string;
design: Design[];
saga: Saga[];
}
}
My Model:
export class PlatformModel implements PlatformInterface.Root {
id: string;
name: string;
design = [];
saga = [];
constructor(obj?: any) {
this.id = obj.name.toLowerCase().replace(' ', '-');
this.name = obj.name;
this.design = obj.design;
this.saga = obj.saga;
}
}
My Service:
#Injectable()
export class PlatformService {
public list$: Observable<PlatformModel[]>;
private _platform: AngularFirestoreCollection<PlatformModel>;
constructor(db: AngularFirestore) {
this._platform = db.collection<PlatformModel>('platforms');
this.list$ = this._platform.valueChanges();
}
/** Get Platform by id */
get(id: string): Observable<PlatformModel> {
return this._platform.doc<PlatformModel>(id).valueChanges();
}
/** Add / Update Platform */
set(id: string, platforms: PlatformModel) {
return fromPromise(this._platform.doc(id).set(platforms));
}
/** Remove Platform */
remove(id: string) {
return fromPromise(this._platform.doc(id).delete());
}
}
My function in Component.ts
constructor(public _platformService: PlatformService) {
}
addPlatform(name: string) {
if (name !== '') {
const platform = new PlatformModel({
name: name,
design: [],
saga: []
});
this._platformService.set(platform.id, platform).subscribe();
}
}
The Angular Compiler dont Throw any error, But when i try to fire the addPlatform Function i get in Browser this error:
ERROR Error: Function DocumentReference.set() called with invalid data. Data must be an object, but it was: a custom PlatformModel object
The Errors Says that the Data must be an object, but it is allready an object or not? i mean i define in the service it with:
public list$: Observable<PlatformModel[]>;
[] Makes it to an object or not?
I've found some clarification here Firestore: Add Custom Object to db
while firebase could send the data inside your object to the database, when the data comss back it cannot instantiate it back into an instance of your class. Therefore classes are disallowed
my workaround for custom class was
this.db.collection(`${this.basePath}/`).doc(custom_class.$key)
.set(Object.assign({}, JSON.parse(JSON.stringify(custom_class))))
.then( ret => {
log.debug('file added', ret);
}).catch( err => {
log.error(err);
});
so I guess in your case it would be
/** Add / Update Platform */
set(id: string, platforms: PlatformModel) {
return fromPromise(this._platform.doc(id).set(Object.assign({},JSON.parse(JSON.stringify(platforms))));
}
For adding a Map into Firestore document you'll have to use:
Object.assign({}, JSON.parse(JSON.stringify(YOUR_MAP)))

How to inject a service in a model class with existing constructors

I have an model class with a default constructor and a constructor with parameters. I also have a service with some methods I would like to use in the model class. I have the include for the service, but when I attempt to inject the service with the constructor for the service, I get
"Multiple constructor implementations are not allowed."
Here is an example of what I have attempted:
import { MyService } from '../utilities/utils.service';
export class MyData {
private __var1: string;
get var1(): string { return this.__var1; }
set var1(val: string) { this.__var1 = val; }
private __var2: string;
get var2(): string { return this.__var2; }
set var2(val: string) { this.__var2 = val; }
// etc.
constructor()
constructor(
var1: string,
var2?: string
) {
this.__var1 = var1;
this.__var2 = var2;
}
constructor(private myService: MyService) { }; // causes error.
}
I thought this was the correct approach, obviously not.

Categories