Angular 5 - How to fill a FormArray - angular-material

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);
});

Related

Rails post controller not passing user association to React view

I'm trying to make a basic CRUD store app with Rails and React, but I'm stuck on displaying the author (user) association of the post. The post itself shows just fine. I'm trying to avoid using jbuilder so I can understand the problem I'm having.
The current show method in the controller, which works:
controllers/post_controller.rb
def show
if post
render json: post
else
render json: post.errors
end
end
The current React view, which works:
app/javascript/components/Post.js
import React from "react";
import { Link } from "react-router-dom";
class Post extends React.Component {
constructor(props) {
super(props);
this.state = { post: { description : '' } };
}
componentDidMount() {
const {
match: {
params: { id }
}
} = this.props;
const url = `/api/v1/show/${id}`;
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.then(response => this.setState({ post: response }))
.catch(() => this.props.history.push("/posts"));
}
render() {
const { post } = this.state;
let descriptionList = "No descriptions present";
if (post.description.length > 0) {
descriptionList = post.description
.split(",")
.map((description, index) => (
<li key={index} className="list-group-item">
{description}
</li>
));
}
return (
<div className="">
<div className="hero position-relative d-flex align-items-center justify-content-center">
<img
src={post.image}
alt={`${post.description} image`}
className="img-fluid position-absolute"
/>
<div className="overlay bg-dark position-absolute" />
</div>
<div className="container py-5">
<div className="row">
<div className="col-sm-12 col-lg-3">
<ul className="list-group">
<h5 className="mb-2">Description</h5>
{descriptionList}
<div>{post.title}</div>
<div>${(post.price * .01).toLocaleString()}</div>
</ul>
</div>
</div>
<Link to="/posts" className="btn btn-link">
Back to all posts
</Link>
</div>
</div>
);
}
}
export default Post;
When I add render json: post, include: :user to the controller and {post.user.email} and render() { const { post, user } = this.state;
to the view, the error message in the console is cannot read property 'email' of undefined. When I try to define the user in the controller method user = post.user.email and in the view {user}, the terminal error is:
NoMethodError (undefined method 'oswaldo#daugherty.info' for #<Post id: 5, title: "Post 5", description: "You can't synthesize the bandwidth without compres...", image: "https://loremflickr.com/300/300/cats 5", price: 883105, rating: nil, review: nil, created_at: "2021-01-31 23:26:03", updated_at: "2021-01-31 23:26:03", user_id: 5>):
I've checked my database and all the associations display correct there. In short, I don't know how to send the post's user association correctly to the view. What am I missing? Any help appreciated because I'm really spinning my wheels on this one.
you might be facing the bug reported bug.
If you are only looking email from related user record, you can use following
# in Post model
delegate :email, to: :user, prefix: true, allow_nil: true
# and while rendering
use Post.as_json(methods: [:user_email])

How to display data from firebase in a loop

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

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

Ember-Data beta 3 and saving hasMany relationships and additonally one record with Rails

I am using Rails 4, Ember 1.2.0 and Ember Data 1.0.0-beta.3 and DS.ActiveModelSerializer.
I have trouble saving a new record with a 'has many' relationship. Two model records should be created but only by one POST request.
I have 2 models which looks like this:
Lingohub.SupportCase = DS.Model.extend({
subject: DS.attr('string'),
status: DS.attr('string'),
created_at: DS.attr('date'),
supportCaseMessages: DS.hasMany('supportCaseMessage')
});
Lingohub.SupportCaseMessage = DS.Model.extend({
supportCase: DS.belongsTo('supportCase'),
text: DS.attr('string'),
created_at: DS.attr('date')
});
My NEW route creates support case and support case massage:
Lingohub.SupportCasesNewRoute = Ember.Route.extend({
model: function() {
var support_case = this.store.createRecord('supportCase');
support_case.get('supportCaseMessages').createRecord();
return support_case;
}
});
My new form looks like this: (I don't know if you can bind an child attribute easier?)
<form {{action 'create' this on="submit"}} id="new_support_case">
{{input value=subject}}
{{!-- {{supportCaseMessages.0.text}} --}}
{{#each supportCaseMessages}}
{{textarea value=text}}
{{/each}}
<input type="submit">
</form>
'create' action in the controller:
Lingohub.SupportCasesNewController = Ember.ObjectController.extend({
actions: {
create: function(supportCase) {
var message = supportCase.get('supportCaseMessages.content')[0];
console.log(message.get('text'))
supportCase.save();
}
}
});
The POST Request to the Server only sends the 'support case'!!
{"support_case"=>{"subject"=>"aaaaaa", "status"=>nil, "created_at"=>nil}}
How can I send the additional record 'support case message' and the relationship ?
I try to answer my own question. If I do the following I get "support_case_messages" embedded in "support_case", is this really the best way to send 'two models' in one POST request?
Lingohub.SupportCaseSerializer = DS.ActiveModelSerializer.extend({
serializeHasMany: function(record, json, relationship) {
var key, jsonKey;
key = relationship.key;
jsonKey = Ember.String.underscore(key);
json[jsonKey] = [];
record.get(key).forEach(function(item) {
return json[jsonKey].push(item);
});
console.log(json)
return json;
}
});

Resources