Angular 2 http - get data from API - api

I'm trying to get data in JSON format from this site https://api.chucknorris.io/ (random joke), but i get an error:ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays. And "DebugContext_ {view: Object, nodeIndex: 11, nodeDef: Object, elDef: Object, elView: Object}"
This is routing.ts
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class RandomService {
constructor(private http: Http) {
console.log('working');
}
getRandomJokes() {
return this.http.get('https://api.chucknorris.io/jokes/random')
.map(res => res.json());
}
}
This is the component i'm trying to get data into
<div *ngFor="let joke of jokes">
<h3>{{joke.value}}</h3>
</div>
`,
providers: [RandomService]
})
export class PocetnaComponent {
jokes: Joke[];
constructor(private jokesService: RandomService){
this.jokesService.getRandomJokes().subscribe(jokes => {this.jokes = jokes});
}
}
interface Joke{
id: number;
value: string;
}

Since what you are receiving is not an array, but an object, therefore the error you are getting is because an object cannot be iterated. You might want to rethink the naming, since we are dealing with just one object, joke would be a more suitable name. But here, let's use jokes.
So you should do is remove the *ngFor completely and simply display the value with:
{{jokes?.value}}
Nothing else is needed :) Notice the safe navigation operator here, which safeguards null and undefined values in property paths. Be prepared to use this a lot in the asynchronous world ;)
Here is more info about the safe navigation operator, from the official docs.

You are not binding an array to the *ngFor, you have to make sure that jokes is actually an array and not a random joke object.
To solve it if it is an object you can just do this.jokes = [jokes].

The url you mention only provides a single value in an object, so you cannot iterate over it.
Maybe make multiple calls like this:
getRandomJokes(n = 10){
return Promise.all(Array(n).fill(0)
.map(() => this.http.get('https://api.chucknorris.io/jokes/random').toPromise()
.then(res => res.json()));
}
and use with promise
this.jokesService.getRandomJokes().then(jokes => {this.jokes = jokes});

you can check if it is an array and then go for ngFor or convert the jokes to array like let jokes = [jokes];
<div *ngIf = "jokes.length > 0" ; else check>
<div *ngFor="let joke of jokes">
<h3>{{joke.value}}</h3>
</div>
</div>
<ng-template #check>
<p>print the value of the object </p>
</ng-template>

May be jokes isn't an array.
<div *ngIf="Array.isArray(jokes)>
<div *ngFor="let joke of jokes">
<h3>{{joke.value}}</h3>
</div>

Related

Vue class components dynamically add component depending on answer from backend

So from the backend I get a array of objects that look kind of like this
ItemsToAdd
{
Page: MemberPage
Feature: Search
Text: "Something to explain said feature"
}
So i match these values to enums in the frontend and then on for example the memberpage i do this check
private get itemsForPageFeatures(): ItemsToAdd[] {
return this.items.filter(
(f) =>
f.page== Pages.MemberPage &&
f.feature != null
);
}
What we get from the backend will change a lot over time and is only the same for weeks at most. So I would like to avoid to have to add the components in the template as it will become dead code fast and will become a huge thing to have to just go around and delete dead code. So preferably i would like to add it using a function and then for example for the search feature i would have a ref on the parent like
<SearchBox :ref="Features.Search" />
and in code just add elements where the ItemsToAdd objects Feature property match the ref
is this possible in Vue? things like appendChild and so on doesn't work in Vue but that is the closest thing i can think of to kind of what I want. This function would basically just loop through the itemsForPageFeatures and add the features belonging to the page it is run on.
For another example how the template looks
<template>
<div class="container-fluid mt-3">
<div
class="d-flex flex-row justify-content-between flex-wrap align-items-center"
>
<div class="d-align-self-end">
<SearchBox :ref="Features.Search" />
</div>
</div>
<MessagesFilter
:ref="Features.MessagesFilter"
/>
<DataChart
:ref="Features.DataChart"
/>
So say we got an answer from backend where it contains an object that has a feature property DataChart and another one with Search so now i would want components to be added under the DataChart component and the SearchBox component but not the messagesFilter one as we didnt get that from the backend. But then next week we change in backend so we no longer want to display the Search feature component under searchbox. so we only get the object with DataChart so then it should only render the DataChart one. So the solution would have to work without having to make changes to the frontend everytime we change what we want to display as the backend will only be database configs that dont require releases.
Closest i can come up with is this function that does not work for Vue as appendChild doesnt work there but to help with kind of what i imagine. So the component to be generated is known and will always be the same type of component. It is where it is to be placed that is the dynamic part.
private showTextBoxes() {
this.itemsForPageFeatures.forEach((element) => {
let el = this.$createElement(NewMinorFeatureTextBox, {
props: {
item: element,
},
});
var ref = `${element.feature}`
this.$refs.ref.appendChild(el);
});
}
You can use dynamic components for it. use it like this:
<component v-for="item in itemsForPageFeatures" :is="getComponent(item.Feature)" :key="item.Feature"/>
also inside your script:
export default {
data() {
return {
items: [
{
Page: "MemberPage",
Feature: "Search",
Text: "Something to explain said feature"
}
]
};
},
computed: {
itemsForPageFeatures() {
return this.items.filter(
f =>
f.Page === "MemberPage" &&
f.Feature != null
);
}
},
methods: {
getComponent(feature) {
switch (feature) {
case "Search":
return "search-box";
default:
return "";
}
}
}
};

Observing Array Properties in Aurelia

I have a property on my class:
class Control {
#bindable households;
get people() {
return households
.map(household => househould.people)
.reduce((g1, g2) => g1.concat(g2), []);
}
}
Which I use to compute a collection of all people[] within all households which is then rendered here:
<ul>
<li repeat.for="person of people">
${person.firstName} ${person.lastName} - ${person.phone}
</li>
</ul>
I need the list to update whenever people are added to a household, OR if any of the rendered properties, firstName, lastName, phone, for any element in the computed collection is updated. How can I do this in Aurelia? If I use #computedFrom() it will not detect changes to elements of the array, and since the list of people in all households is dynamic, I cannot just create an observer for each element without creating a system for managing when observers should be subscribed / unsubscribed.
Use Dirty Checking
Leave off #computedFrom() and you'll achieve the desired behavior.
export class App {
#bindable households;
get people() {
const households = this.households || []; // Make sure househoulds is defined.
return households.reduce((people, household) => people.concat(household.people), []);
}
}
https://gist.run/?id=040775f06aba5e955afd362ee60863aa
Right as I was about to give up on being able to Google for a solution, Aurelia Signaling came to the rescue. This code ended up working for me:
<ul>
<li repeat.for="person of people">
<!-- May work without this rendering method,
this is just closer to what my actual code is doing. -->
${renderPersonInfo(person) & signal: 'example-signal'}
</li>
</ul>
And the class:
import {BindingSignaler} from 'aurelia-templating-resources';
#inject(BindingSignaler)
class Control {
#bindable households;
constructor(bindingSignaler) {
this.bindingSignaler = bindingSignaler;
//Obviously, you can have this trigger off any event
setInterval(() => this.bindingSignaler.signal('example-signal'), 1000);
}
get people() {
return households
.map(household => househould.people)
.reduce((g1, g2) => g1.concat(g2), []);
}
}
You must avoid dirty-checking as far as possible, signals are the perfect option for your scenario. Just bear in mind that if you want to use computedFrom on an array you can do so by watching its length property for instance rather than dirtyChecking, sth like the following #computedFrom("myArray.length")

Angular: Getting the last 4 posts from Contentful

Hi I'm trying to get the last four blog posts from contentful api in angular 5. I've created a service and I'm capable of retrieving 1 with the following code.
getContent(contentId) {
const promise = this.client.getEntry(contentId);
return Observable.fromPromise(promise).map(entry => entry.fields);
};
I would like to return it as an observable, however if returned as a promise, I would like to know how to work with a promise in the component.ts and -.html.
getLastByCount(number) {
var promise = this.client.getEntries({
limit: number
})
...
}
If i do the same for getting multiple entries I get a 'PromiseObservable', which contains a 'promise: ZoneAwarePromise'. In which it has 'items: Array', where object are as when i log the single entry. How do I work which such objects?
Edited:
I've done as suggested by: Stephan
getLastByCount(number) {
var promise = this.client.getEntries({
limit: number
})
return Observable.fromPromise(promise).mergeMap((collection) => (
Observable.from(collection.items)
))
}
And in my component.ts in OnInit()
posts$: Observable<any>;
this.posts$ = this.contentfulService.getLastByCount(4).map(entry => entry.fields);
In my component.html, i do this when displaying the one entry
<div *ngIf="post$ | async as post">
<h1>{{ post.headline }}</h1>
<time>Published on {{ post.published | date: 'fullDate' }}</time>
<hr>-->
</div>
I try this when using the collection:
<div *ngIf="posts$ | async as posts">
<ul>
<li class="post" *ngFor="let post of posts$ | async">
<h1>{{ post.headline }}</h1>
<time>Published on {{ post.published | date: 'fullDate' }}</time>
<hr>
</li>
</ul>
</div>
I get this error: Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
What you probably want is an Observable that emits every single item of the collection that the Contentful client returns.
To do so, you can use the mergeMap functionality:
Observable
.fromPromise(promise)
.mergeMap((collection) => (
Rx.Observable.from(collection.items)
))
Now you can work with every item in the observable sequence as with the single entry above, e.g. you can
.map(entry => entry.fields)
EDIT:
Turns out Angular expects you to have an Observable<Entry[]>, instead of Observable<Entry>, so the proper way is:
Observable
.fromPromise(promise)
.map((collection) => collection.items)

Aurelia iterate over map where keys are strings

I'm having trouble getting Aurelia to iterate over a map where the keys are strings (UUIDs).
Here is an example of the data I'm getting from an API running somewhere else:
my_data = {
"5EI22GER7NE2XLDCPXPT5I2ABE": {
"my_property": "a value"
},
"XWBFODLN6FHGXN3TWF22RBDA7A": {
"my_property": "another value"
}
}
And I'm trying to use something like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="[key, value] of my_data" class="list-group-item">
<span>${key} - ${value.my_property}</span>
</li>
</ul>
</div>
</template>
But Aurelia is telling me that Value for 'my_data' is non-repeatable.
I've found various answer by googling, but they have not been clearly explained or incomplete. Either I'm googling wrong or a good SO question and answer is needed.
As another resource to the one supplied by ry8806, I also use a Value Converter:
export class KeysValueConverter {
toView(obj) {
if (obj !== null && typeof obj === 'object') {
return Reflect.ownKeys(obj).filter(x => x !== '__observers__');
} else {
return null;
}
}
}
It can easily be used to do what you're attempting, like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="key of my_data | keys" class="list-group-item">
<span>${key} - ${my_data[key]}</span>
</li>
</ul>
</div>
</template>
The easiest method would be to convert this into an array yourself (in the ViewModel code)
Or you could use a ValueConverter inside repeat.for as described in this article Iterating Objects
The code...
// A ValueConverter for iterating an Object's properties inside of a repeat.for in Aurelia
export class ObjectKeysValueConverter {
toView(obj) {
// Create a temporary array to populate with object keys
let temp = [];
// A basic for..in loop to get object properties
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...in
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
temp.push(obj[prop]);
}
}
return temp;
}
}
/**
* Usage
* Shows how to use the custom ValueConverter to iterate an objects properties
* aka its keys.
*
* <require from="ObjectKeys"></require>
* <li repeat.for="prop of myVmObject | objectKeys">${prop}</li>
*/
OR, you could use the Aurelia Repeat Strategies provided by an Aurelia Core Team member
You'd have to import the plugin into your app.
Then you'd use it using the pipe syntax in your repeat.for....like so....
<div repeat.for="[key, value] of data | iterable">
${key} ${value.my_property}
</div>

Aurelia: validating form with reusable validatable custom element

Short question: How can I validate a parent form when the validation is part of child custom elements?
Long version:
I built a reusable custom element which includes validation which is working like I expect it to do:
validated-input.html:
<template>
<div class="form-group" validate.bind="validation">
<label></label>
<input type="text" value.bind="wert" class="form-control" />
</div>
</template>
validated-input.js:
import { bindable, inject } from 'aurelia-framework';
import { Validation } from 'aurelia-validation';
#inject(Validation)
export class ValidatedInputCustomElement {
#bindable wert;
constructor(validation) {
this.validation = validation.on(this)
.ensure('wert')
.isNotEmpty()
.isGreaterThan(0);
}
}
I will have some forms that will use this custom element more than once in the same view (can be up to 8 or 12 times or even more). A very simplified example could look like this:
<template>
<require from="validated-input"></require>
<form submit.delegate="submit()">
<validated-input wert.two-way="val1"></validated-input>
<validated-input wert.two-way="val2"></validated-input>
<validated-input wert.two-way="val3"></validated-input>
<button type="submit" class="btn btn-default">save</button>
</form>
</template>
In the corresponding viewmodel file I would like to ensure that the data can only be submitted if everything is valid. I would like to do something like
this.validation.validate()
.then(() => ...)
.catch(() => ...);
but I don't understand yet how (or if) I can pull the overall validation into the parent view.
What I came up with up to now is referencing the viewmodel of my validated-input like this:
<validated-input wert.two-way="val1" view-model.ref="vi1"></validated-input>
and then to check it in the parent like this:
this.vi1.validation.validate()
.then(() => ...)
.catch(() => ...);
but this would make me need to call it 8/12/... times.
And I will probably have some additional validation included in the form.
Here is a plunkr with an example:
https://plnkr.co/edit/v3h47GAJw62mlhz8DeLf?p=info
You can define an array of validation objects (as Fabio Luz wrote) at the form level and then register the custom element validations in this array. The validation will be started on the form submit.
The form code looks like:
validationArray = [];
validate() {
var validationResultsArray = [];
// start the validation here
this.validationArray.forEach(v => validationResultsArray.push(v.validate()));
Promise.all(validationResultsArray)
.then(() => this.resulttext = "validated")
.catch(() => this.resulttext = "not validated");
}
validated-input.js gets a new function to register the validation
bind(context) {
context.validationArray.push(this.validation);
}
The plunker example is here https://plnkr.co/edit/X5IpbwCBwDeNxxpn55GZ?p=preview
but this would make me need to call it 8/12/... times.
And I will probably have some additional validation included in the form.
These lines are very important to me. In my opinion (considering that you do not want to call 8/12 times, and you also need an additional validation), you should validate the entire form, instead of each element. In that case, you could inject the validation in the root component (or the component that owns the form), like this:
import { Validation } from 'aurelia-validation';
import { bindable, inject } from 'aurelia-framework';
#inject(Validation)
export class App {
val1 = 0;
val2 = 1;
val3 = 2;
resulttext = "";
constructor(validation) {
this.validation = validation.on(this)
.ensure('val1')
.isNotEmpty()
.isGreaterThan(0)
.ensure('val2')
.isNotEmpty()
.isGreaterThan(0)
.ensure('val3')
.isNotEmpty()
.isGreaterThan(0);
//some additional validation here
}
validate() {
this.validation.validate()
.then(() => this.resulttext = "valid")
.catch(() => this.resulttext = "not valid");
}
}
View:
<template>
<require from="validated-input"></require>
<form submit.delegate="validate()" validation.bind="validation">
<validated-input wert.two-way="val1"></validated-input>
<validated-input wert.two-way="val2"></validated-input>
<validated-input wert.two-way="val3"></validated-input>
<button type="submit" class="btn btn-default">validate</button>
</form>
<div>${resulttext}</div>
</template>
Now, you can re-use the validated-input component in other places. And of course, you probably have to rename it, because the name validated-input does not make sense in this case.
Here is the plunker example https://plnkr.co/edit/gd9S2y?p=preview
Another approach would be pushing all the validation objects into an array and then calling a function to validate all validations objects, but that sounds strange to me.
Hope it helps!