How to display data from firebase in a loop - firebase-realtime-database

I have data in firebase, I would like to display them using a loop.
items: any;
constructor(db: AngularFireDatabase) {
db.list(`/users/`).snapshotChanges()
.subscribe(o => { this.items = o; console.log(o) });
}
<div *ngFor="let item of items;">
Items: {{item.key}}
<p *ngFor="let device of item;">{{device.name}}</p>
</div>
I can't display the second loop, NgFor only supports binding to Iterables such as Arrays. How to convert it?

In firebase, retrieving data via snapshotChanges() includes metadata with actual data in form of {key, payload, prevKey, type}.
In user case, you are trying to access item.name, but it is undefined.
You have to extract the payload before iterating. You can try something like this.
items$: any;
constructor(db: AngularFireDatabase) {
this.items$ = db.list(`/users/`).snapshotChanges()
.pipe(map(changes => changes.map(c => ({
key: c.key,
payload: c.payload.val(),
type: c.type,
prevKey: c.prevKey
}))));
}
<div *ngFor="let item of items$ | async;">
Items: {{item.key}}
name: {{item.payload.name}}
</div>
If you don't need key, use valueChanges() instead of snapshotChanges().
For more details https://github.com/angular/angularfire2/blob/master/docs/rtdb/lists.md

Related

Angular 5 - How to fill a FormArray

I will fill the customerNumberContainers which looks like this:
this.form = new FormGroup({
customerNumberContainers: new FormArray([
new FormGroup({
contactTenant: new FormControl('', [Validators.required, Validators.minLength(2)]),
customerNumber: new FormControl('', [Validators.required, Validators.minLength(2)])
}),
]),
Therefore I do this after I get the values over
this.contactService.findContactById(this.id).subscribe(response => { ...
Set values into form:
let customerNumberContainersFormArray: FormArray = this.form.controls.customerNumberContainers as FormArray;
customerNumberContainersFormArray.controls["0"].controls.contactTenant.value = 'TestValue';
but it is not shown with:
in Controller:
get customerNumberContainers(): FormArray {
return this.form.get("customerNumberContainers") as FormArray;
}
in Template:
<div formArrayName="customerNumberContainers">
<div *ngFor="let customerNumberContainer of customerNumberContainers.controls; index as i" [formGroupName]="i">
<mat-input-container class="full-width-input">
<input matInput formControlName="contactTenant">
</mat-input-container>
</div>
Does anyone known what I am doing wrong. It seems for me that values with *ngFor arn't refreshed.
why dont You just patch whole form with model ? like this:
set up your model, for example:
export class Tenants {
id: number;
customerNumberContainers: TenantContact[];
}
export class TenantContact {
contactTenant: string;
customerNumber: string;
}
fetch it from service like u always do but it should match above models and patch whole form (or setValue)
this.contactService.findContactById(this.id).subscribe((tenats: Tenants) => {
this.form.patchValue(tenats);
});

react json Objects are not valid as a React child

I am using the React-Rails gem and accessing a json object items from a Rails controller.
Rails controller:
class ItemsController < ApplicationController
def index
#items = Item.all
render json: #items
end
end
My React App component accesses these items and attempts to pass it as a prop to a child component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: {},
activeTab: 'items'
};
}
componentDidMount() {
$.getJSON('/items.json', (response) => {
this.setState({ items: response })
});
}
render () {
return (
<div>
<ItemsContent items={this.state.items}>
</div>
);
}
}
And this child component looks like this:
class ItemsContent extends React.Component {
render () {
return (
<div>
<div>Items: {this.props.items}</div>
</div>
);
}
}
ItemsContent.propTypes = {
items: React.PropTypes.object
};
And I get this error:
react.js?body=1:1324 Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `ItemsContent`.
How do I get around this? Is there a way to easily use JSON objects in my React components?
Right now I tried wrapping the JSON object in an array:
tabbedContent = <ItemsContent items={[this.state.items]}></ItemsContent>;
Since this.state.items is an array, you are unable to dump out all the items in an array like that. You can use the javascript array API and iterate over the items and display them like so :
class ItemsContent extends React.Component {
render () {
return (
<div>
{
this.props.items.map(function(item) {
return <div> Item : {item} </div>
}
</div>
);
}
}
ItemsContent.propTypes = {
items: React.PropTypes.object
};
If you are only getting back a single object every time then map will not work and you need to break out the object by property in order to display everything :
render () {
return (
<div>
<div> Item : {this.props.items.a} , {this.props.items.b}, {this.props.items.c} </div>
</div>
);
}
You can iterate over the list in render() of App Component. And create a React.Component Item for each of the items.
App.js
render () {
return (
<div>
this.state.items.map( function(item){
<Item value={item} key={}>
});
</div>
);
}
In Item.js
class Item extends React.Component {
render () {
return (
<div>
return <div> Item : {this.props.value} </div>
</div>
);
}
}
Item.propTypes = {
value: React.PropTypes.object
};

React.js - Rails: Set State

I have two components: App and Registration Form
The form has two inputs: Name and Last name
Looking at the App state in dev. tools I see length: undefined and name: "name entered". I'm not getting any errors but I'm missing the last name.
This is only happening in Rails. I tried the same code in a non rails environment and it works fine. I'm using this gem for React: gem 'react-rails', '~> 1.5.0' and running Rails 4.2.4
var App = React.createClass({
getInitialState : function(){
return {
registrations: {}
}
},
addRegistration : function(registration){
// create unique id
var timestamp = (new Date()).getTime();
// update state
this.state.registrations['registration-' + timestamp] = registration;
//set the state
this.setState({ registrations : this.state.registrations });
},
render : function(){
return (
<RegistrationForm addRegistration={this.addRegistration}/>
)
}
});
var RegistrationForm = React.createClass({
createRegistration : function(event){
// prevent default
event.preventDefault();
// take data from form and create object
var registration = {
name : this.refs.name.value,
lastname : this.refs.lastname.value
}
// Add registration to App Object
this.props.addRegistration(registration);
this.refs.registrationForm.reset();
//console.log(registration);
},
render : function(){
return (
<div className="col-sm-12">
<form action="" className="form" ref="registrationForm" onSubmit={this.createRegistration}>
<div className="form-group">
<label >Name</label>
<input className="form-control" ref="name"/>
</div>
<div className="form-group">
<label >Last Name</label>
<input className="form-control" ref="lastname"/>
</div>
<div>
<button className="btn btn-primary">Submit</button>
</div>
</form>
</div>
)
}
});
App = React.createFactory(App)
What I'm trying to do is to give each registration a unique id number based on the time stamp.
When I console log the following:
addRegistration : function(registration){
// create unique id
var timestamp = (new Date()).getTime();
// update state
this.state.registrations['registration-' + timestamp] = registration;
//set the state
this.setState({ registrations : this.state.registrations });
},
I can see a registration object the way I want it. I can add as many unique registrations to the App state but each registration has length: undefined, name: "name" , but it's missing the last name.
If I change the set state to this:
this.setState({ registrations : registration });
This gives me a single registration with name and last name but it doesn't add multiple registrations. It only creates one registration which gets update every time I submit the add registration form.
this.state.registrations['registration-' + timestamp] = registration;
You seem to be mutating the state directly, based on the React Docs https://facebook.github.io/react/docs/component-api.html
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
setState() does not immediately mutate this.state but
creates a pending state transition. Accessing this.state after calling
this method can potentially return the existing value.
There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
setState() will always trigger a
re-render unless conditional rendering logic is implemented in
shouldComponentUpdate(). If mutable objects are being used and the
logic cannot be implemented in shouldComponentUpdate(), calling
setState() only when the new state differs from the previous state
will avoid unnecessary re-renders.
Try cloning the current state then use that as the argument.
// if array
var clonedRegistration = this.state.registrations.slice();
clonedRegistration['registration-' + timestamp] = registration;
this.setState({registrations: clonedRegistration})
or
this.setState({registrations: {['registration-'+ timestamp]: registration} });
I think the answer Road put was close.
First set your initial state to an array.
getInitialState: function(){
return { registrations: []}
}
your addRegistration function
addRegistration : function(registration){
I think this is what you're missing:
//getting current state
var oldRegistrations = this.state.registrations;
Otherwise I believe you're updating the same thing over and over, instead of adding a new registration object. Then push your registration. You should set the timestamp
// update state
oldRegistrations.push(registration);
var registrations = oldRegistrations;
//set the state
this.setState({ registrations : registrations });
},
I would advise creating the id somewhere in here since you're not using an actual ajax call to a rails db:
var registration = {
name : this.refs.name.value,
lastname : this.refs.lastname.value
id: (new Date()).getTime();
}
I'm not sure I understand your question regarding your form values or if you were having trouble with them. But if you were I think doing something like this may help:
<input type='text' className='form-control'
placeholder='Name' name='name'
value={this.state.name} onChange={this.handleChange} >
</input>
<input type='text' className='form-control'
placeholder='Last Name' name='last name'
value={this.state.last_name} onChange={this.handleChange} >
</input>
Then implement a handleChange function within the same component to constantly handle the form's values onChange. That should look like this:
handleChange: function(e) {
var name = e.target.name;
var obj = {};
obj[name] = e.target.value;
this.setState(obj);
}
Hope this helps,

Ember direct URL or page refresh empty model

I have an index page with different courses. From that index page you can navigate to a specific course by a link-to. When I navigate to a course everything works fine but when I refresh the page or go to that URL directly the model is empty.
This is how my code looks like:
index.hbs ---------------------------------------
<div class="row">
<div class="col-md-6 col-md-offset-3 text-center">
<h1>Become a Tjuna Fish</h1>
<img src="http://placehold.it/500x300">
<p>Leer met de technieken werken die bij Tjuna worden gebruikt en ontwikkel jezelf tot een echte Tjuna Fish!</p>
</div>
</div>
<div class="row">
<h1 class="text-center">Cursussen</h1>
{{#each}}
<div class="col-md-4 text-center">
<div class="row">
<img {{bind-attr src="img"}}/>
</div>
<div class="row">
{{#link-to "course" this}}{{title}}{{/link-to}}
</div>
</div>
{{/each}}
</div>
scripts ---------------------------------------
BecomeTjunaFish.Router.map(function () {
// Add your routes here
this.resource('index', {path: '/'});
this.resource('course', { path: ':url'});
});
BecomeTjunaFish.IndexRoute = Ember.Route.extend({
// admittedly, this should be in IndexRoute and not in the
// top level ApplicationRoute; we're in transition... :-)
model: function () {
return this.store.find('course');
}
});
BecomeTjunaFish.CourseRoute = Ember.Route.extend({
// admittedly, this should be in IndexRoute and not in the
// top level ApplicationRoute; we're in transition... :-)
model: function (params) {
return this.store.find('course', params.id);
}
});
BecomeTjunaFish.Course = DS.Model.extend({
title: DS.attr('string'),
img: DS.attr('string'),
goal: DS.attr('string'),
targetGroup: DS.attr('string'),
prerequisites: DS.attr('string'),
url: DS.attr('string')
});
BecomeTjunaFish.Course.FIXTURES = [
{
id: 1,
title: 'Tjuna Basis',
img: 'http://placehold.it/200x200',
goal: 'kunnen werken met de basis tools en opmaaktalen die Tjuna gebruikt',
targetGroup: 'frontend developers in wording',
prerequisites: 'geen',
url: 'basis_cursus'
},
{
id: 2,
title: 'Tjuna Frontend',
img: 'http://placehold.it/200x200',
goal: '',
targetGroup: '',
prerequisites: '',
url: 'frontend_cursus'
},
{
id: 3,
title: 'Tjuna Backend',
img: 'http://placehold.it/200x200',
goal: '',
targetGroup: '',
prerequisites: '',
url: 'backend_cursus'
}
];
You need to specify the dynamic segment as :id in your router. What happens is,
When you transition via {{link-to}}, you pass the entire model object. Hence while retrieving the course model(this.store.find('course', params.id);) in route#model , you have the id with you and thereby fetching the model with no trouble.
When you hit back or refresh the course page, all you have is the course url in the address bar URL. This course url (note the entire course object) will be passed to the course route#model hook where you try to retrieve using the id. Hence it blows up
So make your dynamic segment as id in the router to make it work. You can also fetch the records with name.
Working Jsbin
As selvagsz explained, I had to change :url to :id in the router.
I also wanted to have nested URL's without nested templates. Something like this:
this.resource('index', {path: '/'});
this.resource('course', { path: ':course_id'}, function(){
this.resource('lesson', {path: ':lesson_id'}, function(){
this.resource('topic', {path: ':topic_id'});
});
});
Problem with this is, when I go to course/lesson url the lesson template will only render when I have an outlet in the course template. I want the course template to be replaced with the lesson template but keep the same nested url.
I fixed this by using the renderTemplate function of Ember like this:
BecomeTjunaFish.LessonRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('lesson', params.lesson_id);
},
renderTemplate: function() {
this.render('lesson', { into: 'application'})
}
});
This works great but when I navigate back, for example to course, it is not working anymore. Instead of only have a courseRoute I also needed a courseIndexRoute which uses the same model as courseRoute and place the renderTemplate in the CourseIndexRoute (same for LessonIndexRoute). Example:
BecomeTjunaFish.CourseRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('course', params.course_id);
}
});
BecomeTjunaFish.CourseIndexRoute = Ember.Route.extend({
model: function () {
return this.modelFor('course');
},
renderTemplate: function() {
this.render('course', { into: 'application'})
}
});
To me it seems to be a lot of code and I don't know if this is the right way to do this. At the moment this is good enough for me, it's working :) But I would appreciate it to have feedback on it and would like to know if there are other / better ways to fix this.
*I used this question as inspiration: Redirecting from edit to parent resource doesn't (re)render template

Is it possible to submit a hidden field and control its value with x-editable?

inside the document of X-editable, we can create a new record, but how to edit an existing record, and post its name and email fields as well as its id =1(this id not changed) to the backend?
<table>
<thead><th>id</th><th>name</th><td>email</th></thead>
<tbody>
<tr><td><span>1</span></td><td><span class='myeditable'>name</span></td><td><span class='myeditable'>email#example.com</span></td></tr>
</tbody>
</table>
$('.myeditable').editable({
type: input,
url: '/edituser'
});
$('#save-btn').click(function() {
$('.myeditable').editable('submit', {
url: '/edituser',
ajaxOptions: {
dataType: 'json' //assuming json response
},
success: function(data, config) {
if(data && data.id) { //record created, response like {"id": 2}
},
error: function(errors) {
}
});
});
I used Angular-xeditable to do this, but the idea is the same I think.
I added a hidden span to my table and gave it an e-name. ng-show sets display:none, which I think is just what you need to do as well.
<span
editable-text="journalEntry._id"
e-name="_id"
e-form="rowform"
ng-show="false">
</span>
I used Angular-xeditable also, but had to change Michael's code because the hidden field appeared (I wanted it to remain hidden) when I edited the row.
Therefore I had to insert
e-class="hidden"
So in the end I had:
<span
e-class="hidden"
editable-text="employee.key"
e-name="key"
e-form="rowform"
ng-show="false">
</span>
To post a hidden field, you could try to modify your
url: '/edituser'
to
url: '/edituser?hidden-name1=hidden-value1&hidden-name2=hidden-value2' and so on...

Resources