I have an array and I need to display several random items from this array.
Now the code looks like this, but I think that there simply should be two different services, one in another.
my component looks like this:
items: Item[];
randomItems: Item[];
ngOnInit() {
this.someService.getItems().subscribe((items) => {
this.items = items;
});
this.randomIndex(this.items);
}
randomItems(items: Item[]) {
return this.randomItems = _.sample(items, _.random(9));
}
}
interface Items {
id: number,
title: string,
body: string
}
my html looks like this:
<ul *ngFor="let item of items">
<li>{{ item.id }}</li>
</ul>
How can I make two different services from this code?
First move the call to randomIndex to inside the subscribe callback.
ngOnInit() {
this.someService.getItems().subscribe((items) => {
this.items = items;
this.randomIndex(this.items);
});
}
About your question:
How can I make two different services from this code?
You don't need to based on the code you have shown. If you do it is subjective as to how you refactor it. Your main defect is that you are not waiting on the service to respond to do something with the result and that is easily solved.
Related
I have a json file that looks like this:
"skills":[
{
"type":"Languages",
"skill":[
{
"name":"Python"
},
{
"name":"Java"
},
{
"name":"JavaScript"
},
{
"name":"HTML"
},
{
"name":"Bash"
},
{
"name":"MySQL"
}
]
},
{
"type": "Flavours",
"skill": [
{
"name":"Reactjs"
},
{
"name":"Angularjs"
},
{
"name":"Agile"
},
{
"name":"Waterfall"
},
{
"name":"Kanban"
}
]
},
{
"type": "Technologies",
"skill": [
{
"name":"Jira"
},
{
"name":" BitBucket"
},
{
"name":"Git"
}
]
}
]
},
And i am trying to render it using a nested mapping function:
var skills = this.props.data.skills.map((skills) =>{
var skill = skills.skill.map(function(skill){
return <li key={skill.name}>{skill}</li>
})
return <ul key={skills.type}>{skills}</ul>
})
However it says "Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead."
So i tried it like this:
var skills = this.props.data.skills.map(function(skills){
var skillsArr = []
var skill = skills.skill.map(function(skill){
var skillArr = []
skillArr.push(<li key={skill.name}>{skill}</li>)
return <span>{skillArr}</span>
})
skillsArr.push(<div key={skills.type}><h3>{skills.type}</h3>
<ul>
{skill}
</ul>
</div>);
return <div>{skillsArr}</div>
})
But this too gives me the exact same error, i dont get what is wrong here because if i do a single mapping of just the skill types it works, it is only when i try to render the inner mapped items does this error occur and break my code
This is how i am calling it btw:
<div className="bars">
<ul className="skills">
{skills}
</ul>
</div>
If we are talking about using React, you should think more about how to organize your code in order to follow a proper component structure, that will let clear what you want to render and how to properly split your data and responsibilities.
Looking to your JSON, we have a set of "skills" that have skills inside it (let's call them "innerSkills").
We can easily split it into 3 components, let's think together:
We can have a List that will render all your Skills.
We can have a Skill that will be responsible for rendering each Skill data, inside it, we will need to render the InnerSkills, so let's split it to another component.
We have then InnerSkill, that will be responsible for rendering each innerSkill that we have for each skill.
So, long story short, what we have is:
List -> Skill -> InnerSkills
Great, now that we established the split, let's see how we can make each component responsible for rendering its data.
Let's say we want to simply call <List skills={data} />. Following this, we can then start on the list itself, which would look something like:
const List = ({ skills }) => (
<ul>
{skills.map((skill, i) => (
<Skill key={i} skill={skill} />
))}
</ul>
);
Now that we are looping through all Skills and calling the Skill component for rendering it, we can take a look at how Skill should look, since it will also need to loop through skill.
const Skill = ({ skill }) => (
<li>
<p>Type: {skill.type}</p>
<ul>
{skill.skill.map((innerSkill, i) => (
<InnerSkill key={i} innerSkill={innerSkill} />
))}
</ul>
</li>
);
Great. Now we already have the two loops you need to render all the data, so it's just missing the definition on how each InnerSkill should look like, and we can take a simplified approach and say we just want to render the name, so it could be something like:
const InnerSkill = ({ innerSkill }) => (
<li>
<p>Name: {innerSkill.name}</p>
</li>
);
To summarize this implementation, I made a simple code sandbox so you can See it live! and play around with the components.
I hope this clarifies your question and helps you to think better in the future on how you want to organize stuff, first check how to split, later how to render. Don't try to start rendering everything inside loops because it will get nasty.
There are two things in your code causing this error:
var skills = this.props.data.skills.map((skills) =>{
var skill = skills.skill.map(function(skill){
// should be <li key={skill.name}>{skill.name}</li>
return <li key={skill.name}>{skill}</li>
})
// should be <ul key={skills.type}>{skill}</ul>
return <ul key={skills.type}>{skills}</ul>
})
Assuming you want a single unordered list of all skill names, I'd suggest using the flatMap() function to re-write this as follows:
<div className="bars">
<ul className="skills">
{this.props.data.skills.flatMap((skillGroup) =>
skillGroup.skill.map((skill) => (
<li key={skill.name}>{skill.name}</li>
))
)}
</ul>
</div>
I'm obtaining a response using GET like this.
constructor(private http: HttpClient) { }
items: Item[];
stuff: any[];
ngOnInit() {
const url = ...;
this.http.get(url)
.subscribe(next => {
console.log(next);
console.log(next.length);
this.items = next;
this.stuff = next;
});
}
I can see that there's 6 elements and what they are (checking the console). However, neither of the fields seem to the the array. I'm trying to iterate out the elements using the ngFor directive but seeing nothing or just a single line.
<div ngFor="let item of items">
->{{item?.id}}
</div>
<div ngFor="let item of stuff">
->{{item?.id}}
</div>
I know that I can resolve it by exposing the data through a service like this but partly, I want to do a quicky here and learn how to do it; partly, I'm going to have the very same problem in the service code instead.
I've tried using map and forEach on the next value but I got the error saying that Object isn't an array. The IDE suggested adding the work array to the syntax so it becomes next.array.forEach but that didn't even got executed, producing a lot of red ink.
What should I do? (Not sure what to google for at this stage.)
donkeyObservArray: Observable<Donkey[]>;
donkeyArray: Array<Donkey>;
this.donkeyObservArray.subscribe(donk=> {
this.donkeyArray = donk;
console.log(this.donkeyArray);
and to be happy...
Or get typed:
donkey: Donkey= null;
getDonkey(): Donkey{
this.donkey = new Donkey();
this._http.get<Donkey>(...your ws link)
.subscribe(data => {
this.donkey = data;
You can either convert the object to an array before passing to template
object-2-array-angular-4
or use a custom pipe to transform object to array
Using non-3rd party plugins:
I have an array like this:
[
{groupName:'General', label:'Automatic Updates', type:'select', values:{0:'On', 1:'Off'}},
{groupName:'General', label:'Restore Defaults', type:'button', values:['Restore']},
{groupName:'General', label:'Export & Import', type:'button', values:['Export', 'Import']},
{groupName:'Timing', label:'Double Click Speed', type:'text'},
{groupName:'Timing', label:'Hold Duration', type:'text'}
]
I want to ng-repeat over this but create groups.
The final result I'm hoping will look like this:
So basically that is a ng-repeat on the groupName to make two div containers, then it ng-repeats for each item within to add the rows.
Is this possible without having to change my array into an object like this:
[
'General': [...],
'Timing': [...]
]
If you want to do something like this, one solution is to split up the repeat into 2 separate repeats. the easiest is to do that by creating a small helper filet that filters out the unique properties. a filter like this would do:
function uniqueFilter() {
return function(arr,property) {
if (Object.prototype.toString.call( arr ) !== '[object Array]') {
return arr;
}
if (typeof property !=='string') {
throw new Error('need a property to check for')
}
return Object.keys(arr.reduce(isUn,{}));
function isUn(obj,item) {
obj[item[property]] = true;
return obj;
}
}
}
That filter will return an array that consist of the unique values of the property you want to group by.
Once you have this you can nest a couple of ngRepeats like this:
<div ng-repeat="group in vm.data| uniqueFilter:'groupName'">
{{group}}
<ul>
<li ng-repeat="item in vm.data| filter:{groupName:group}">{{item.label}}</li>
</ul>
</div>
And you should be set.
There is no need to pull in a 3rth party for this.
see it in action in this plunk.
How do you get a single item from a GoInstant GoAngular collection? I am trying to create a typical show or edit screen for a single task, but I cannot get any of the task's data to appear.
Here is my AngularJS controller:
.controller('TaskCtrl', function($scope, $stateParams, $goKey) {
$scope.tasks = $goKey('tasks').$sync();
$scope.tasks.$on('ready', function() {
$scope.task = $scope.tasks.$key($stateParams.taskId);
//$scope.task = $scope.tasks.$key('id-146b1c09a84-000-0'); //I tried this too
});
});
And here is the corresponding AngularJS template:
<div class="card">
<ul class="table-view">
<li class="table-view-cell"><h4>{{ task.name }}</h4></li>
</ul>
</div>
Nothing is rendered with {{ task.name }} or by referencing any of the task's properties. Any help will be greatly appreciated.
You might handle these tasks: (a) retrieving a single item from a collection, and (b) responding to a users direction to change application state differently.
Keep in mind, that a GoAngular model (returned by $sync()) is an object, which in the case of a collection of todos might look something like this:
{
"id-146ce1c6c9e-000-0": { "description": "Destroy the Death Start" },
"id-146ce1c6c9e-000-0": { "description": "Defeat the Emperor" }
}
It will of course, have a number of methods too, those can be easily stripped using the $omit method.
If we wanted to retrieve a single item from a collection that had already been synced, we might do it like this (plunkr):
$scope.todos.$sync();
$scope.todos.$on('ready', function() {
var firstKey = (function (obj) {
for (var firstKey in obj) return firstKey;
})($scope.todos.$omit());
$scope.firstTodo = $scope.todos[firstKey].description;
});
In this example, we synchronize the collection, and once it's ready retrieve the key for the first item in the collection, and assign a reference to that item to $scope.firstTodo.
If we are responding to a users input, we'll need the ID to be passed from the view based on a user's interaction, back to the controller. First we'll update our view:
<li ng-repeat="(id, todo) in todos">
{{ todo.description }}
</li>
Now we know which todo the user want's us to modify, we describe that behavior in our controller:
$scope.todos.$sync();
$scope.whichTask = function(todoId) {
console.log('this one:', $scope.todos[todoId]);
// Remove for fun
$scope.todos.$key(todoId).$remove();
}
Here's a working example: plunkr. Hope this helps :)
i know in Angular world it is better to bind data than manipulate dom elements. but i can't figure out a way to implement the 'in timeline, click a tweet, load replies, click another tweet load another replies' effects.
here is some code run into my thoughts:
<div class="tweet" ng-repeat="tweet in tweets">
<div class="tweet-content">{{tweet}}</div>
<a class="button" ng-click="loadreplay()">load reply</a>
<div class="reply-container">{{reply}}</div>
</div>
if i write controller like this
app.controller('Test', function($scope){
$scope.tweets = ["foo", "bar"];
$scope.loadreplay = function(){
$scope.reply = "reply";
}
});
then all {{reply}} fields will be filled with 'reply', so in this condition, is manipulate the dom elements the only resolution? or some more "angular" way?
Use a appropriate schema for your data/model. Considering that you would store not only the text but at least something like a ID you would use an object anyway. So think about something like this:
$scope.tweets = [
{ id:1, txt: 'foo' },
{ id:2, txt: 'bar' }
]
Then you could store the individual replies in that object as well:
$scope.loadreply = function(tweet) {
tweet.reply = 'Reply';
}
Note: In this function you could then also use the ID to e.g. fetch the tweets from the server like this:
$scope.loadreply = function(tweet) {
tweet.reply = LoadReplies(tweet.id);
}
You would then use the tweet specific reply attribute for display:
<div ng:repeat="tweet in tweets">
<div>{{tweet.txt}}</div>
<a ng:click="loadreply(tweet)">load reply</a>
<div>{{tweet.reply}}</div>
</div>
See this fiddle for a working demo: http://jsfiddle.net/XnBrp/