I'm trying to put a JSON object into an array after an API call.
First I made my API call and then I try to add every user into in a formatted JSON object.
connectionProvider.ts
import { UserModelProvider } from './../user-model/user-model';
import { MSAdal, AuthenticationContext, AuthenticationResult } from '#ionic-native/ms-adal';
export class MsConnectionProvider {
userInfo : UserModelProvider;
users: UserModelProvider[];
constructor(...) {}
getUserInfo(){
let header = new Headers({
Authorization: this.accessToken;
});
let options = new RequestOptions({headers: header});
this.usersSubscription = this.http.get("https://graph.microsoft.com/v1.0/groups/**ID**/members", options).map(res => res.json()['value']);
this.usersSubscription.subscribe(res => {
for (let user of res){
this.addUserInfo(user.displayName, user.jobTitle, "something", user.mail);
}
});
}
addUserInfo(name, job, departement, mail){
this.userInfo = new UserModelProvider;
this.userInfo.name = name;
this.userInfo.job = job;
this.userInfo.departement = departement;
this.userInfo.mail = mail;
this.users.push(this.userInfo);
}
}
userModelProvider.ts
export class UserModelProvider {
name: string;
job: string;
departement: string;
mail: string;
photo: any;
}
The problem is when I try to push "this.userInfo = new UserModelProvider" into this.users array the function block and nothing happens.
I certainly don't understand the class, can you help me?
Thank you.
You can't push to an array that has not been initialised.
you need to change:
users: UserModelProvider[]
to:
users: UserModelProvider[] = [];
Also (may or may not help):
Nothing is probably happening because push mutates the array and as such angular change detection may not kick in.
Instead of using push, create a new array with:
this.users = [...this.users, this.userInfo]
or in ES5:
this.users = this.users.concat([this.userInfo])
Create an instance of the class before assigning the values,
this.userInfo = new UserModelProvider();
Related
I have a js object in which I return my endpoint addresses from api. This is a very nice solution for me, it looks like this:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
},
};
Now I can call this address in my redux-saga to execute the xhr query:
import { api } from 'utils';
const requestURL = api.users.notifications;
But I'm a bit stuck because now I have a problem - base path is missing here: '/users'.
Now when I call api.users, then I get a object. I would like to have a default value after calling the object like:
import { api } from 'utils';
const requestURL = api.users; // http://localhost:3000/Users
const requestURL2 = api.users.notifications; // http://localhost:3000/Users/notifications
I know that I could add a new string with the name 'base' to the object and add '/Users' there, but I don't like this solution and I think, there is a better solution.
You could do one of the following:
extend the String class
const API_BASE_URL = "http://localhost:3000"
const USERS = "/Users"
class UsersEndpoints extends String {
constructor(base) {
super(base)
}
// this is still a proposal at stage 3 to declare instance variables like this
// if u want a truly es6 way you can move them to the constructor
checkEmail = (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
notifications = `${API_BASE_URL}${USERS}/notifications`
messages = `${API_BASE_URL}${USERS}/messages`
}
// you can use userEndpoints itself as a string everywhere a string is expected
const userEndpoints = new UsersEndpoints(API_BASE_URL)
export default {
users: userEndpoints
}
The previous is just actually equivalent to
...
const userEndpoints = new String(API_BASE_URL)
userEndpoints.notifications = `${API_BASE_URL}${USERS}/notifications`
...
Obviously this is not recommended: you should not extend native classes, there are many disadvantages to this approach.
An obvious example is that there could be a conflict between the properties you use and the properties that might be brought by the native class
override the toString method
...
export default {
users: {
checkEmail: (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`,
notifications: `${API_BASE_URL}${USERS}/notifications`,
messages: `${API_BASE_URL}${USERS}/messages`,
toString: () => API_BASE_URL
},
};
// this is actually not much different than the previous method, since a String is an objet with an overridden toString method.
// That said this method is also not recommended since toString is used in many places in native code, and overriding it just to substitute a string value will make information get lost in such places, error stacks for example
Achieve what u want using the language features intended for such a use case
What you are asking is to make the same variable to have different values in the same time, which is not possible in the language syntax, and it makes sense because it makes it hard to reason about code.
that being said i recommend something of the following nature
// it is also better to use named exports
export const getUsersEndpoint = ({
path = "",
dynamicEndpointPayload = {},
} = {}) => {
switch (path) {
case "notifications":
return `${API_BASE_URL}${USERS}/notifications`
case "messages":
return `${API_BASE_URL}${USERS}/messages`
case "checkEmail":
return `${API_BASE_URL}${USERS}/${dynamicEndpointPayload.email}/checkEmail`
// you still can do checkEmail like this, but the previous is more consistent
// case "checkEmail":
// return (email) => `${API_BASE_URL}${USERS}/${email}/checkEmail`
default:
return `${API_BASE_URL}`
}
}
// you can use it like this
getUsersEndpoint() // returns the base
getUsersEndpoint({path: 'notifications'})
You can extend prototype to achieve this behaviour:
export const API_BASE_URL = 'http://localhost:3000';
export const USERS = '/Users';
const users = `${API_BASE_URL}${USERS}`
const baseUrls = {
checkEmail: (email) => `${users}/${email}/checkEmail`,
notifications: `${users}/notifications`,
messages: `${users}/messages`,
}
Object.setPrototypeOf(users.__proto__, baseUrls);
export default {
users
};
Try having object will all user endpoint and a function that return a value of a end point
const user = {
default: '/users',
notification: '/notification',
profile: '/profile',
getEndPoint(prop) {
if(this[prop] === 'default' ){
return this[prop];
} else {
if(this[prop]) {
return this.default + this[prop];
}
}
}
}
So you can have more end points that come under user and you can simply call
const requestURL = api.user.getEndPoint('default'); // http://localhost:3000/Users
const requestURL2 = api.user.getEndPoint('notifications'); // http://localhost:3000/Users/notification
I'm looking for a way to wait for the user to stop interaction and then make an HTTP request, for this I'm looking into the debounceTime() operator from RxJs, but the target I'm waiting for is an array I defined.
This is the scenario:
export class KeywordSelectionComponent implements OnInit {
constructor(private proposalService: ProposalService) { }
#ViewChild(MatTable, {static: true}) kwTable: MatTable<any>;
#ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
#Input() proposalId: string;
keywordInput = new FormControl(null, Validators.required);
dataSource: MatTableDataSource<Keyword>;
displayedColumns = ['action', 'keyword', 'searches', 'competition', 'cpc'];
suggestedKeywords: Keyword[] = [];
selectedKeywords: string[] = [];
fetchSuggestions(seeds?: string[]) {
const ideas = {
seeds: [],
limit: 25
};
this.proposalService.getKeywordIdeas(this.proposalId, ideas).pipe(retry(3)).subscribe(res => {
this.suggestedKeywords = res;
});
}
}
I'm not including the whole component here, but the idea is the following:
I have a list of suggestedKeywords which I render on the page, each of these should call an addKeyword() method to add that keyword to the dataSource, and after that, I call the fetchSuggestions() method to get new keywords to populate the suggestedKeywords list.
The problem comes when I try to select multiple keywords in quick succession, since that would trigger a request for each of those clicks to update the suggestedKeywords list, so I wanted to use the debounceTime() to prevent the request to trigger until the user stops clicking items for a bit; however this requires an Observable to be the element changing as far as I know, but in my case, it is just a simple array.
Is there someway to keep track of the value of the array so it waits for a while after it changes before making the HTTP request, like an Observable?
EDIT: Used the from() operator as suggested in the comments, in order to actually listen to changes do I need to define other methods? I'm thinking something similar to valueChanges() in FormControls.
Going through more documentation I'm leaning towards Subject, BehaviorSubject, etc; but I'm not sure if this would be a correct approach, could anyone provide an example on how to do this?
Wrap your array in Observable.of() RxJS operator and it will behave like observable
What I ended up doing was using a Subject to keep track of the changes, calling it's next() function evrytime a modified the suggestedKeywords array and subscribing to it as an observable.
My component ended up looking like this:
export class KeywordSelectionComponent implements OnInit {
constructor(private proposalService: ProposalService) { }
keywordInput = new FormControl(null, Validators.required);
suggestedKeywords: Keyword[] = [];
selectedKeywords: string[] = [];
isLoadingResults = false;
tag$ = new Subject<string[]>();
ngOnInit() {
this.tag$.asObservable().pipe(
startWith([]),
debounceTime(500),
switchMap(seeds => this.getSuggestionsObservable(seeds))
).subscribe(keywords => {
this.suggestedKeywords = keywords;
});
}
addSuggestedKeyword(keyword: Keyword) {
const suggestedKeyword = keyword;
const existing = this.dataSource.data;
if (!existing.includes(suggestedKeyword)) {
existing.push(suggestedKeyword);
this.dataSource.data = existing;
}
this.tag$.next(this.getCurrentTableKeywords());
}
fetchKeywordSearch(keyword: string) {
this.isLoadingResults = true;
this.keywordInput.disable();
const search = {
type: 'adwords',
keyword
};
const currentData = this.dataSource.data;
this.proposalService.getKeywordSearch(this.proposalId, search).pipe(retry(3)).subscribe(res => {
currentData.push(res);
this.dataSource.data = currentData;
}, error => {},
() => {
this.isLoadingResults = false;
this.keywordInput.enable();
this.tag$.next(this.getCurrentTableKeywords());
});
}
getCurrentTableKeywords(): string[] {}
getSuggestionsObservable(seeds: string[] = []): Observable<Keyword[]> {
const ideas = {
type: 'adwords',
seeds,
limit: 25
};
return this.proposalService.getKeywordIdeas(this.proposalId, ideas).pipe(retry(3));
}
}
I am creating an app in angular, and one task is, I need read data from Firestore and save into an array, my code snippet is as below:
public getListingData(): Observable < RequestListingModel > {
console.log("getting listing...");
this.firestore.collection('Requests').snapshotChanges().subscribe(
requests => {
this._listRequestItems = requests.map(a => {
const data = a.payload.doc.data() as RequestItemModel;
data.requestId = a.payload.doc.id;
console.log("doc found - " + data.requestId);
return data;
})
});
const requestListingModel = {
items: this._listRequestItems
}
as RequestListingModel;
return of(requestListingModel);
}
And my code of RequestListingModel is as below:
import * as dayjs from 'dayjs';
export class RequestItemModel {
name: string;
address: string;
category: string;
requestId: string;
postBy: string;
description: string;
// Default mock value
// expirationDate = '12/01/2018';
expirationDate: string = dayjs().add(5, 'day').format('MM/DD/YYYY HH:mm:ss') as string;
}
export class RequestListingModel {
items: Array<RequestItemModel> = [];
constructor(readonly isShell: boolean) { }
}
And it's not working as I always get empty return when call the function getListingData(), but the console can print out the each requestId successfully, I think something wrong with the way I store into array, please help, thanks!
Your problem is happening because things are happing asynchronously:
So return of(requestListingModel); gets called before this.firestore.collection('Requests').snapshotChanges().subscribe(...) populates the array, thus returning an observable of an empty array.
Since it looks like you want to return an observable, don't subscribe() inside your method. Just do a map() on the snapshotChanges observable to transform the output, and let the consuming code subscribe to your getListingData() method.
public getListingData(): Observable < RequestListingModel > {
console.log("getting listing when someone subscribes...");
return this.firestore.collection('Requests').snapshotChanges().pipe(
map(requests => {
let requestListingModel = {
items: []
} as RequestListingModel;
requests.map(a => {
let data = a.payload.doc.data() as RequestItemModel;
data.requestId = a.payload.doc.id;
console.log("doc found - " + data.requestId);
requestListingModel.items.push(data);
});
return requestListingModel;
})
);
}
Note: still... can't test this at the moment, so it may not be 100% correct :-\
But hopefully points you in the right direction. :-)
I'm working in Angular with firebase.
I want to create a function which will receive two arguments string and object and returns an Observable of filtered data according to object key-value pairs from specific collection dynamically.
For example when I will call it this way I expect this result:
dynamicFilter('users',{name: 'Jack', age: 30})
It should evaluate this call:
firebase.collection('users',ref => ref.where('name','==','Jack').where('age','==',30))
So I can subscribe to it like:
dynamicFilter('users',{name: 'Jack', age:30}).subscribe(res => {
console.log(res)
})
I want the function to be universal as I don't know in advance how many conditions will be passed through the object.
I tried this:
constructor(private db: AngularFirestore){}
dynamicFilter(collectionName: string, options: object){
let targetCollection = this.db.firestore.collection(collectionName)
let query;
let keys = Object.keys(options)
for(let i = 0; i < keys.length; i++ ){
if(i === 0){
query = targetCollection.where(keys[i],'==',options[keys[i]])
}else{
query = query.where(keys[i],'==',options[keys[i]])
}
}
return query
}
So what can do with this returned value? It is an instance of Query class and I guess I can call method get, which returns a promise like query.get().then(res => console.log(res)).
But the res is another instance of QuerySnapshot class. How can I simply get data?
To get the data from a QuerySnapshot:
querySnapshot.forEach(function(doc) {
console.log(doc.data());
});
So:
dynamicFilter('users',{name: 'Jack', age:30}).subscribe(querySnapshot => {
querySnapshot.forEach(function(doc) {
console.log(doc.data());
});
})
For more on this, see:
the Firebase documentation on getting multiple documents from a collection.
the reference documentation for `QuerySnapshot.
I've been searching all the resources you can imagine , and finally got rid of it and started to experiment on my own , and it's done , it works just perfect you wouldn't have even subscribe to it, it triggers via Callback function.
Usecase:
1.You write the code below in service to keep things clear and reusable.
For example let's call it DataService:
import { Injectable } from '#angular/core';
import { AngularFirestore } from '#angular/fire/firestore';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private fire: AngularFirestore) { }
dynamicQueryFilter(collectionName: string, options: object, callbackFn: Function): void{
const bindedCallback: Function = callbackFn.bind(this)
const targetCollection = this.fire.collection(collectionName).ref
const optionPairs = Object.entries(options)
let queryMap = targetCollection.where(optionPairs[0][0],'==',optionPairs[0][1])
optionPairs.slice(1).forEach(item => {
queryMap = queryMap.where(item[0],'==',item[1])
})
queryMap.get()
.then(resolve => {
const documents = []
resolve.forEach(doc => documents.push(doc.data()))
bindedCallback(documents)
})
}
2.Then inject it in your component
export class AppComponent implements OnInit {
constructor(private db: DataService){}
users: User[];
ngOnInit(){
this.db.dynamicQueryFilter
(
'users',
{name: 'Jack', age: 30, profession: 'Programmer', isMaried: true},
(res) => this.product = res
)
}
}
3.Render all married programmers whose name is Jack and who 30 years old in your template
<div *ngIf="users">
<div *ngFor="let user of users">
<h2>{{user.name}} {{user.surname}}</h2>
<hr>
<h4>Additional Info</h4>
<div><b>Age: </b>{{user.age}}</div>
<div><b>Profession: </b>{{user.profession}}</div>
<div><b>Adress: </b>{{user.adress}}</div>
<div><b>Phone: </b>{{user.phone}}</div>
//and so on....
</div>
</div>
With this tool in your hand you can do any filtration you may imagine DYNAMICALLY.
You can bind arguments of dynamicQueryFilter to inputs or any forms in your template, and let the user perform interactive realtime multi filtration.
Special thanks to Frank van Puffelen for sharing his knowledge.
I have a http request that gets this Json object from a nosql database:
let jsonBody = {
birthday : 1997,
firstname: 'foo',
lastname:'bar'
}
Then I want to load this information into the Student model:
class Student{
constructor(){
}
getFullname(){
return this.lastname+' '+this.firstname
}
getApproxAge(){
return 2018- this.birthday
}
}
Normally, I would add this method to this class:
fromJson(json){
this.studentId = json.studentId;
this.birthday = json.birthday;
this.firstname = json.firstname;
this.lastname = json.lastname;
}
I would use it as follow:
let student = new Student()
student.fromJson(jsonBody)
console.log(student.getFullname())
console.log(student.getApproxAge())
This works fine but my problem is I have: 100 proprieties in reality. Will I have to write all proprities one by one in the fromJson method?
And also, if a propriety name has change, let's say: lastname became LastName, I will have to fix it?
Is there a simpler way to just assign these values to the object student dynamically but keep all of its methods??
Something like this:
fromJson(json){
this = Object.assign(this, json) //THIS IS NOT WORKING
}
Just assign to an instance:
static from(json){
return Object.assign(new Student(), json);
}
So you can do:
const student = Student.from({ name: "whatever" });
Or make it an instance method and leave away the assignemnt:
applyData(json) {
Object.assign(this, json);
}
So you can:
const student = new Student;
student.applyData({ name: "whatever" });
It could also be part of the constructor:
constructor(options = {}) {
Object.assign(this, options);
}
Then you could do:
const student = new Student({ name: "whatever" });
And also, if a property name has changed, let's say: lastname became LastName, I will have to fix it?
Yes you will have to fix that.
There is no way in javascript to deserialize json into classes. So I wrote a library ts-serializable that solves this problem.
import { jsonProperty, Serializable } from "ts-serializable";
export class User extends Serializable {
#jsonProperty(String)
public firstName: string = ''; // default value necessarily
#jsonProperty(String, void 0)
public lastName?: string = void 0; // default value necessarily
#jsonProperty(Date)
public birthdate: Date = new Date(); // default value necessarily
public getFullName(): string {
return [
this.firstName,
this.lastName
].join(' ');
}
public getAge(): number {
return new Date().getFullYear() - this.birthdate.getFullYear();
}
}
const user: User = new User().fromJSON(json);
user.getFullName(); // work fine and return string
user.getAge(); // work fine and return number
// or
const user: User = User.fromJSON(json);
user.getFullName(); // work fine and return string
user.getAge(); // work fine and return number
The library also checks types during deserialization.