Angular: How to get async data for template - html

I have the following problem:
I want to make a table with entries (Obj). And some of them have a file attribute.
If they have a file attribute (entry.file) I want to make a backend call to get the url of that file:
public getFileURL(archiveID: string, documentID: string, sysID: string){
const request: FileRequest = {
archiveID: archiveID,
documentID: documentID,
sysID: sysID
};
this.fileService.file(request).subscribe(response => {
if (response) {
return response;
}
})
}
This is called like: getFileURL(entry.file.archiveID, entry.file.documentID, entry.file.sysID)
And it should return an Observable, so I can check if i got a backend response.
<tr *ngFor="let entry of period.claims; let i = index">
...
<td>
<div *ngIf="entry.file">
<div *ngIf="fileClientService.getFileURL(entry.file.archiveID, entry.file.documentID, entry.file.sysID) | async as file; else loading">
<a target="about:blank" class="download" (click)="clickLink(file)"></a>
</div>
<ng-template #loading let-file>loading..</ng-template>
</div>
</td>
All I want is to display "loading" until the url is loaded and then display the a-tag.
Also, the url parameter coming back from the backend could be empty. So i also need to display nothing if the url is empty ("").
At the moment it fires hundred of backend calls for 2 objects with the entry.file property :(
I am not that good with Observables and I hope someone can help me with that.
Thank you so far :)

You need to return Observable directly from your method and map your period.claims into one Observable:
// add proper type
entries: Observable<...> = getEntries();
getEntries() {
// we map every claim to Observable returned from getFileURL method
const entries = period.claims.map(entry =>
getFileURL(...).pipe(
// we use map to return whole entry from Observable - not only url
map(url => ({
...entry,
url,
}))
));
// forkJoin will return one Observable with value array when each Observable is completed
return forkJoin(...entries);
}
public getFileURL(archiveID: string, documentID: string, sysID: string): Observable<...> {
const request: FileRequest = {
archiveID: archiveID,
documentID: documentID,
sysID: sysID
};
return this.fileService.file(request).pipe(filter(Boolean));
}
If you want not to pass to template empty response you could use filter operator and pass Boolean as callback. It will return only truthy values. You can read more about it: https://www.learnrxjs.io/learn-rxjs/operators/filtering/filter
You can read also more about forkJoin: https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin
Note that adding proper type to method would tell you what you're doing wrong ;)

Related

Firestore data() method does not exist on JSON parsed document after JSON stringified

I am building a FlashCard website using NextJs and firebase. I have a homepage which I want to render server side and so I am using getServerSideProps. InsidegetServerSideProps function, I am fetching all the documents of the current user from firestore and is stored in an array and is returned as props as below:
export const getServerSideProps = async(ctx: GetServerSidePropsContext) {
let notes: DocumentData[];
// fetch documents here and populate notes array like so [doc,doc,doc,..]
// data() method works here and returns document fields
console.log(notes[0].data());
// NextJs throws error "`object` ("[object Object]") cannot be serialized as JSON. Please only return JSON serializable data types.", so I have to JSON.stringify() the notes
return {
props: {
notes: JSON.stringify(notes),
}
}
}
Below, I have my homepage where I parse the JSON string and have access to the notes, but now the data() method on the document does not exist/works and throws method does not exist errors. If I have to access the document fields, I have to use the dot operator on every property of the document till I reach the fields property which is nested deep down in the object as follows:
export default function Home({ notes }) {
let docs = JSON.parse(notes); // can access notes
// data() method throws function does not exist error
console.log(docs[0].data());
// I am only able to access document fields as below
console.log(docs[0]._document.data.value.mapValue.fields);
return (
<Layout>
<HomeContent notes={docs}/>
</Layout>
);
}
I have searched everywhere and found nothing that helped me why data() method won't work. If I directly fetch the documents inside the page component on client side, the data() method on the document returns its' fields. I don't know how using JSON serializations affect it. I would always prefer to use data() method to access fields than to go that deep plus I am planning to fetch data on server on other pages as well.
I would really appreciate if you can shed some light on it. It took all of my days time.
EDIT: The code that gets notes from firestore:
// inside getServerSideProps
let notes: DocumentData[] = null;
const getNotes = async(ref: DocumentReference < DocumentData > , uid: string) => {
let tempNotes = [];
const categoriesSnapshot = await getDoc < DocumentData > (ref);
const categoriesObject = categoriesSnapshot.data();
// return if user doesn't have any documents
if (!categoriesObject) {
return;
}
const promises: [] = categoriesObject.categories.map((category: string) => {
const userCategoriesRef = collection(database, 'CategoryCollection', uid, category);
return getDocs(userCategoriesRef); // returns a promise
});
const allQuerySnapshots = await Promise.all < DocumentData > (promises);
allQuerySnapshots.forEach((querySnapshot) => {
tempNotes.push(...querySnapshot.docs);
});
notes = tempNotes;
}
const categoryDocRef = doc(database, "CategoryCollection", uid);
await getNotes(categoryDocRef, uid);

Trying to dispatch an axios action with a string array as the payload, but keep getting 'undefined' at the reducer?

I'm new to web development and as part of a project have made a Django React Redux app. The frontentd has a component with form entry - when a form is submitted, the component calls an action that sends an axios request to a python twitter crawler script in the backend. The script returns a response containing a string array (the tweets are inside the array). I then try to dispatch the array contained in the response.data, as a payload to a reducer. However I keep getting an error saying that the payload of what I have dispatched is undefined.
This is the return statement of the python method that is called - corpus is a string array.
return JsonResponse({"results:": corpus})
This is the code for the action that sends a GET request to the python method. It's then returned the string array inside a json object.
// GET_TWEETS - get list of tweets from hashtag search
export const getTweets = (hashtag) => dispatch => {
axios
.get('http://localhost:8000/twitter_search', {
params: {text: hashtag}
})
.then(res => {
console.log(res.data); //check if python method returns corpus of tweets
//const results = Array.from(res.data.results);
dispatch({
type: GET_TWEET,
payload: res.data.results
});
})
.catch(err => console.log(err));
}
The console log shows that the object is returned successfully from the python script.
console log
This is the code for my reducer. I want my action to contain the string array and then assign that string array to 'tweets' in the state, so that I can return this to a component and then access the array from the component and then display contents of thisx array of tweets on the frontend
import { GET_TWEET } from '../actions/types';
const initialState = {
tweets: []
}
export default function(state = initialState, action) {
console.log(action.payload)
switch(action.type) {
case GET_TWEET:
return {
...state,
tweets: [...action.payload]
}
default:
return state;
}
}
Also, this is some of the code for the component that I want to receive the string array, I hope I have set this part up properly:
export class Tweets extends Component {
static propTypes = {
tweets: PropTypes.array.isRequired,
getTweets: PropTypes.func.isRequired
}
...
const mapStateToProps = state => ({
tweets: state.TweetsReducer.tweets
});
export default connect(mapStateToProps, { getTweets })(Tweets);
Here is the error I get from the console. Console logging the payload of the action also shows that its value is undefined: undefined error
I've been trying to solve this for several days now, but am completely lost and I have a hunch the solution to this is pretty simple...

Axios request get value from nested array in Vue

In a .vue file I am trying the get value from an axios get response result which is in a nested array. The code looks like the example below (without a search form for query).
<div class="results" v-if="results">
<p>{{ results }}</p>
<p>{{ result }}</p>
</div>
<script>
import axios from 'axios';
export default {
name: 'search',
data () {
return {
query '',
results: '',
result: ''
}
},
methods: {
getResults(query) {
axios.get('https://apiexample.com/api/search.php?t_id=' + query).then( response => {
this.results = response.data.items;
this.result = response.data.items[0]['1:B'];
});
}
}
}
So for this.results I get something similar to
[{"1:A":10,"1:B":20,"1:C":30,"1:D":40,"1:E":50},
{"1:A":20,"1:B":30,"1:C":40,"1:D":50,"1:E":60},
{"1:A":30,"1:B":40,"1:C":50,"1:D":60,"1:E":70},
{"1:A":40,"1:B":50,"1:C":60,"1:D":70,"1:E":80}]
For this.result I am getting undefined when I am trying to get the value of 20. Probably navigating that type of response incorrectly or perhaps something more specific needs to be added to data() {}?
Any help would be appreciated.
Thanks.
As noted in the comments, response.data.items is a string, not an object. This seems like a flawed API response, with the items unnecessarily encoded as a JSON string within the response.
However, assuming that fixing the problem in the server is not possible, the items can be decoded in the UI:
this.results = JSON.parse(response.data.items);
this.result = this.results[0]['1:B'];

Deserializing json in Angular 2/4 using HttpClientModule

So I'm getting the following JSON structure from my asp.net core api:
{
"contentType": null,
"serializerSettings": null,
"statusCode": null,
"value": {
"productName": "Test",
"shortDescription": "Test 123",
"imageUri": "https://bla.com/bla",
"productCode": null,
"continuationToken": null
}
}
I have the following typescript function that invokes the API to get the above response:
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; //not working...
console.log("DATA: " + data);
console.log("DATA: " + data['value']);
return data;
});
}
ProductVM:
export interface ProductVM {
productName: string;
shortDescription: string;
imageUri: string;
productCode: string;
continuationToken: string;
}
My problem is that I can't deserialize it to ProductVM. The console logs just produce [object Object]
How can I actually map the contents of the value in my json response to a ProductVM object?
Is it wrong to say that data is a ProductVM in the map function? I have tried lots of different combinations but I cannot get it to work!
I'm unsure whether I can somehow automatically tell angular to map the value array in the json response to a ProductVM object or if I should provide a constructor to the ProductVM class (it's an interface right now), and extract the specific values in the json manually?
The data object in the map method chained to http is considered a Object typed object. This type does not have the value member that you need to access and therefore, the type checker is not happy with it.
Objects that are typed (that are not any) can only be assigned to untyped objects or objects of the exact same type. Here, your data is of type Object and cannot be assigned to another object of type ProductVM.
One solution to bypass type checking is to cast your data object to a any untyped object. This will allow access to any method or member just like plain old Javascript.
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map((data: any) => this.externalProduct = data.value);
}
Another solution is to change your API so that data can deliver its content with data.json(). That way, you won't have to bypass type checking since the json() method returns an untyped value.
Be carefull though as your any object wil not have methods of the ProductVM if you ever add them in the future. You will need to manually create an instance with new ProductVM() and Object.assign on it to gain access to the methods.
From angular documentation: Typechecking http response
You have to set the type of returned data when using new httpClient ( since angular 4.3 ) => this.http.get<ProductVM>(...
public externalProduct: ProductVM;
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get<ProductVM>("api/product?productCode=" + code)
.map((data: ProductVM) => {
this.externalProduct = data; // should be allowed by typescript now
return data;
});
}
thus typescript should leave you in peace
Have you tried to replace
this.externalProduct = data;
with
this.externalProduct = data.json();
Hope it helps
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get("api/product?productCode=" + code)
.map(data => {
this.externalProduct = <ProductVM>data;
console.log("DATA: " + this.externalProduct);
return data;
});
}
So, first we convert the response into a JSON.
I store it into response just to make it cleaner. Then, we have to navigate to value, because in your data value is the object that corresponds to ProductVM.
I would do it like this though:
Service
getProductExternal(code: string): Observable<ProductVM> {
return this.http.get(`api/product?productCode=${code}`)
.map(data => <ProductVM>data)
.catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
Component
this.subscription = this.myService.getProductExternal(code).subscribe(
product => this.externalProduct = product,
error => console.warn(error)
);
I used this approach in a client which uses the method
HttpClient.get<GENERIC>(...).
Now it is working. Anyway, I do not understand, why I do not receive a type of T back from the http client, if I don't use the solution provided in the answer above.
Here is the client:
// get
get<T>(url: string, params?: [{key: string, value: string}]): Observable<T> {
var requestParams = new HttpParams()
if (params != undefined) {
for (var kvp of params) {
params.push(kvp);
}
}
return this.httpClient.get<T>(url, {
observe: 'body',
headers: this.authHeaders,
params: requestParams
}).pipe(
map(
res => <T>res
)
);
}

Receiving JSON-Data via http: empty response

I'm new to Angular2 and somehow it's really hard to me to understand how http works in Angular2. I made a simple component which should display a json response. It doesn't work and I have no idea why. I checked many tutorials and tried it with promises as well as observables. Doesn't work. I just can't get the data of the response.
My code:
private doAction() {
this.doHttpRequest().subscribe(
data => this.content = data
);
this.content = JSON.stringify(this.content);
}
private doHttpRequest() {
return this.http.get('http://jsonplaceholder.typicode.com/posts/1')
.catch(this.handleError);
}
this.content is bind to my template. When I click a button to start doAction() for a second I see "" in the template, after another second [object Object]
What is the problem here?
That's the expected behavior
private doAction() {
// schedule HTTP call to server and subscribe to get notified when data arrives
this.doHttpRequest().subscribe(
// gets called by the observable when the response from the server aarives
data => this.content = data
);
// execute immediately before the call to the server was even sent
this.content = JSON.stringify(this.content);
}
To fix it change it to
private doAction() {
this.doHttpRequest().subscribe(
data => {
//this.content = data;
this.content = data.json());
});
);
}
If you want code to be executed after data arrived, then you need to move it inside the subscribe(...) callback.
Since http requests are asynchron you have to put all your logic depending on the results of the http call in the subscribe() callback like this:
private doAction() {
this.doHttpRequest().subscribe(
data => {
this.content = data;
// any more logic must sit in here
}
);
}
private doHttpRequest() {
return this.http.get('http://jsonplaceholder.typicode.com/posts/1')
.map(res => res.json());
.catch(this.handleError);
}
Http call is returning data since it shows "[object Object]" in template. If you want to see the json data in template you can use the json pipe as below.
{{content | json}}
PS: No need of "this.content = JSON.stringify(this.content);" in your code.