I am leveraging Angular 4's FormBuilder and FormGroup class to create a Form. The form's content is supplied by a GET call with the Questions and Answers structured via JSON format.
Dabbling with my localhost, I see the following error from my web console on page load:
Could anyone with Angular Forms expertise correct this implementation and explain where I went wrong?
Web Console indicates this node from the HTML is causing it:
<fieldset [formGroupName]="i">
For sake of brevity, I'm just providing the bare-bone snippets only down below. Let me know if you need more details.
HTML component:
<form id="form-clinicalscreener" [formGroup]="clinicalForm" (submit)="submitScreenerForm(clinicalForm)" novalidate>
<div formArrayName="Questions">
<div class="section" *ngFor="let tab of clinicalQuestionsResults; let i = index;">
<h2 class="form-title">{{tab.DisplayText}}</h2>
<div>
<div class="form-group" *ngFor="let question of tab.Questions">
<fieldset [formGroupName]="i">
<label>{{question.DisplayText}}</label>
<input type="hidden" formControlName="QuestionId" value="{{question.Id}}" data-value="{{question.Id}}">
<div [ngSwitch]="question.AnswerType">
<div *ngSwitchCase="'Radio'" class="form-group-answers">
<div class="radio" *ngFor="let answer of question.Answers">
<input type="radio"
formControlName="AnswerId"
value="{{answer.Id}}"
data-value="{{answer.Id}}"
class="radio-clinicalscreener">
<label class="label-answer">
<span class="label-answer-text">{{answer.DisplayText}}</span>
</label>
</div>
</div>
<div *ngSwitchCase="'DropDown'" class="form-group-answers">
<select formControlName="AnswerId" class="form-control">
<option value="{{answer.Id}}" data-value="{{answer.Id}}" *ngFor="let answer of question.Answers">{{answer.DisplayText}}</option>
</select>
</div>
</div>
</fieldset>
</div>
</div>
</div>
</div>
<div class="text-center">
<button class="btn btn-screener" type="submit" [disabled]="!clinicalForm.valid">Submit</button>
</div>
TypeScript component:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder } from '#angular/forms';
#Component({
selector: '',
templateUrl: '',
styleUrls: ['']
})
export class FormComponent implements OnInit {
// Our model driven form.
clinicalForm: FormGroup;
// Screener Data from API call to be used by view in for loop.
clinicalQuestionsResults: any;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
// Let's get Clinical Screener metadata.
this.loadClinicalScreener();
// Initialize the form here.
this.clinicalForm = this.formBuilder.group({
Questions: this.formBuilder.array([
this.initQuestions()
])
});
}
private initQuestions() {
// initialize our Questions
return this.formBuilder.group({
QuestionId: [''],
AnswerId: ['']
});
}
private loadClinicalScreener() {
// An AJAX call from a forkJoin() to return the JSON payload, then I assign it to a local object, screenerResponse.
let screenerResponse: any = data[0];
this.clinicalQuestionsResults = screenerResponse.Result.Tabs;
console.log('Here is the metadata for Clinical Screener --->', this.clinicalQuestionsResults);
}
}
Related
I am currently developing a dynamic form builder it has an admin side and a user side, on the admin side you can set the name of the input and type eg: first name and textbox you can add or take away from fields and there are a set amount of types you can choose from, when you are done the admin will then save the form to the Database. On the user frontend the from is pulled from the Database and using patchValue the from is set the problem I am having is with the user's input to the questions on the form. I am trying to figure out how I can first set the type and placeholder of the input that the user uses to answer the questions.
As well as dynamically apply validation rules to each input, so, for instance, a user wants to fill out a form with their first name, last name, age, and CV for the first and last name I want to have required and a regex expression to only allow certain characters, for the age I want to have a min and max number and cv required. and when the user submits the form I only want to send the name and value of an input and not the whole form.
But keep in mind that these forms are completely dynamic so I need to validate the user's input based on the input type that is set
/*Typescript Code*/
import { Component, OnInit } from '#angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '#angular/forms';
import { FormService} from '../form.service';
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-reactive',
templateUrl: './reactive.component.html',
styleUrls: ['./reactive.component.scss']
})
export class ReactiveComponent implements OnInit {
public form: FormGroup;
public fieldList: any;
types: Array<any>;
formData: any;
Param: string;
get contactFormGroup() {
return this.form.get('inputs') as FormArray;
}
constructor(
private route: ActivatedRoute,
private fb: FormBuilder,
private api: FormService) { }
ngOnInit() {
/*First I initlize the form*/
this.form = this.fb.group({
name: [null, Validators.compose([Validators.required])],
organization: [null, Validators.compose([Validators.required])],
inputs: this.fb.array([this.createForm()])
});
this.route.paramMap.subscribe(params => {
this.Param = params.get('id');
this.getForm(this.Param);
});
// set fieldslist to this field
this.fieldList = this.form.get('inputs') as FormArray;
}
// formgroup
/*The I initilize the form array of inputs*/
createForm(): FormGroup {
return this.fb.group({
type: [null, Validators.compose([Validators.required])],/*These come from the DB*/
name: [null, Validators.compose([Validators.required])],/*These come from the DB*/
value: [null, Validators.compose([Validators.required])] /*This gets filled in by the user*/
});
}
/*The I use my API service to call the DB using the form Id*/
getForm(id) {
this.api.getForm(id).subscribe(
(data: any) => this.setForm(data)
);
}
getFieldsFormGroup(index): FormGroup {
const formGroup = this.fieldList.controls[index] as FormGroup;
return formGroup;
}
/*Here I set my form data*/
setForm(data) {
const d = data.results;
this.form.patchValue({
name: [d[0].form_name],
organization: [d[0].org],
});
this.form.setControl('inputs', this.setExistingFields(d[0].fields));
}
/*Here I set my inputs array data*/
setExistingFields(fields: any): FormArray {
const formArray = new FormArray([]);
this.fieldList = formArray;
fields.forEach(f => {
formArray.push(this.fb.group({
name: f.name,
type: f.type,
value: f.value
}));
});
return formArray;
}
submit() {
/* Here when I send my form back to the server I only want to send the inputs name and value nothing else */
console.log(this.form.value.inputs);
}
}
/*HTML CODE*/
<div class="container p-1">
<div class="row">
<div class="col-12">
<form class="md-form" [formGroup]="form" #submittion (submit)="submit()">
<div class="card">
<div class="card-header">Form Name</div>
<div class="card-body">
<div class="row">
<div class="form-group col-6">
<i class="fab fa-wpforms prefix"></i>
<input class="form-control" placeholder="Form Name" formControlName="name" type="text" readonly>
</div>
<div class="form-group col-6">
<i class="far fa-building prefix"></i>
<input class="form-control" placeholder="Organization" formControlName="organization" type="text"
readonly>
</div>
</div>
</div>
<div class="card-header col-12">Please fill in all fields. </div>
<div class="card-body" formArrayName="inputs">
<div class="row">
<div class="col-6" *ngFor="let contact of contactFormGroup.controls; let i = index;">
<div [formGroupName]="i" class="row z-depth-1 m-1 p-1">
<div class="form-group col-6">
<input class="form-control" formControlName="name" type="text" readonly>
</div>
<div class="form-group col-6">
<1-- This is the input that the user will fill in and I am not sure how to dynamically set the type or placeholder this is what I am trying to achieve -->
<input class="form-control" type="" placeholder=""
formControlName="value" />
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="icon-bar round">
<button class="btn btn-success round m-1" type="submit" (click)="submit()"><i class="fas fa-save"> Save Form</i></button>
</div>
Any help is very much appricated on this I have been trying to figure it out for a while now, thank you
I have three components: GalleryAddComponent to add a new element, GalleryItemComponent, to edit an element, FieldsComponent, the form I want to use in the components: GalleryAddComponent and GalleryItemComponent. All components are inside the GalleryComponent. But when I go to the component GalleryAddComponent to add a new element I get the error: ERROR TypeError: Cannot read property 'controls' of undefined. Also in the component: GalleryItemComponent.
Help solve this problem so that the editing and adding logic works correctly.
template of GalleryAddComponent
<div class="card">
<div class="card-body">
<form [formGroup]="angForm" novalidate>
<app-fields [formGroup]="angForm"></app-fields>
<div class="form-group but-group">
<button (click)="addPost(title.value, url.value); angForm.reset(title.value, url.value)"
[disabled]="angForm.pristine || angForm.invalid"
class="btn btn-primary">Add
</button>
<a routerLink="/" class="btn btn-danger">Back</a>
</div>
</form>
</div>
</div>
code of GalleryAddComponent
export class GalleryAddComponent implements OnInit {
angForm: FormGroup;
isAdded: boolean = false;
constructor(private fb: FormBuilder, private galleryService: GalleryService) {}
ngOnInit() {
this.angForm = this.fb.group({
title: ['', Validators.required],
url: ['', Validators.required]
});
}
addPost(title: string, url: string): void {
this.galleryService.add(title, url).subscribe(res => {
this.isAdded = true;
});
}
}
template of GalleryItemComponent
<div class="card" *ngIf="toggleEdit">
<h4>Edit your post</h4>
<div class="card-body">
<form [formGroup]="angForm" novalidate>
<app-fields [formGroup]="angForm"></app-fields>
<div class="form-group but-group">
<input type="button"
(click)="updatePost(title.value, url.value)"
[disabled]=" angForm.invalid"
class="btn btn-primary" value="Update Post">
</div>
</form>
</div>
</div>
code of GalleryItemComponent
export class GalleryItemComponent implements OnInit {
pic: Picture;
angForm: FormGroup;
constructor(private route: ActivatedRoute,
private galleryService: GalleryService, private fb: FormBuilder) {}
ngOnInit() {
this.angForm = this.fb.group({
title: ['', Validators.required],
url: ['', Validators.required]
});
this.showPost();
}
showPost(): void {
this.route.params.subscribe(params => {
this.galleryService.getPicture(params['id']).subscribe(res => {
this.pic = res;
this.angForm.setValue({title: res.title, url: res.url})
})
})
}
updatePost(title: string, url: string): void {
this.route.params.subscribe(params => {
this.galleryService.update(title, url, params['id']).subscribe(res => {
if (res.id === this.pic.id) {
this.pic.title = title;
this.pic.url = url;
}
});
});
}
}
template of FieldsComponent
<div [formGroup]="formGroup">
<div class="form-group">
<label class="col-md-4">Picture Title</label>
<input type="text" class="form-control" formControlName="title" minlength="1" #title/>
</div>
<div *ngIf="angForm.controls['title'].invalid && (angForm.controls['title'].dirty || angForm.controls['title'].touched)"
class="alert alert-danger">
<div *ngIf="angForm.controls['title'].errors.required">
Title is required.
</div>
</div>
<div class="form-group">
<label class="col-md-4">Picture Address (url)</label>
<input type="url" class="form-control" formControlName="url" #url pattern="https?://.+"
title="Include http://"/>
</div>
<div *ngIf="angForm.controls['url'].invalid && (angForm.controls['url'].dirty || angForm.controls['url'].touched)"
class="alert alert-danger">
Address(url) is required.
<div *ngIf="angForm.controls['url'].errors.required ">
</div>
</div>
</div>
code of FieldsComponent
export class FieldsComponent implements OnInit {
#Input() formGroup: FormGroup;
constructor() {}
ngOnInit() {}
}
You are getting this error because you are referencing angForms.controls in your FieldsComponent which only has one variable: formGroup.
Simply replace angForms in the template HTML code with formGroup and the issue should be resolved.
<div [formGroup]="formGroup">
<div class="form-group">
<label class="col-md-4">Picture Title</label>
<input type="text" class="form-control" formControlName="title" minlength="1" #title />
</div>
<div *ngIf="formGroup.controls['title'].invalid && (formGroup.controls['title'].dirty || formGroup.controls['title'].touched)"
class="alert alert-danger">
<div *ngIf="formGroup.controls['title'].errors.required">
Title is required.
</div>
</div>
<div class="form-group">
<label class="col-md-4">Picture Address (url)</label>
<input type="url" class="form-control" formControlName="url" #url pattern="https?://.+" title="Include http://" />
</div>
<div *ngIf="formGroup.controls['url'].invalid && (formGroup.controls['url'].dirty || formGroup.controls['url'].touched)"
class="alert alert-danger">
Address(url) is required.
<div *ngIf="formGroup.controls['url'].errors.required ">
</div>
</div>
</div>
I think the easy solution here, is to set up the formgroup in the parent component, and pass it on to the child components as input, hence it is given to the child component before the child component html is loaded.
The other solution I would recommend is to use the Resolve Technique offered by Angular, this allows you to load all data before a component is loaded with a quite straight-forward setup.
Here are some references:
https://www.techiediaries.com/angular-router-resolve/
https://alligator.io/angular/route-resolvers/
Cannot read property 'controls' of undefined. This error can come only coz instead of formGroup you have used angForm. Try replacing it as shared by #Wrokar. Also what I understand is you want to show consolidated error messages. So instead of doing it in html for each formcontrol, you should do it in component.ts and make it more generic, by subscribing to value change of each control like below, and show the consolidated error message.
for (const field in this.formGroup.controls) { // 'field' is a string
const control = this.form.get(field); // 'control' is a FormControl
control.valueChanges.subscribe(
(res) => {
// check if it is pristine, dirty, invalid, touched, pattern match if
any
// you can access control.errors
// create the consolidated list and append it in the list of error
messages
}
)
}
Its better to make it generic coz, tomorrow your form can have additional fields and then you dont have to change you FieldsComponent.
I have a basic form that I have built with 3 fields. Name, description and id. The data is meant to be populated by a service that calls out to a C# API that returns JSON that is then loaded to the form.
When it opens I get the correct data but I get this error. If I striped out the name input, the errors just move to the description field then the id.
I am still learning Angular, but have read a heap to try and fix this. I think I've got it all right, but I'm guessing it's something to do with initialisation because if I add this code before getting the data from the scenario service, the errors go.
this.scenario = { 'id': 0,
'name': '',
'description': '',
'dateCreated': '',
'createdBy': '',
'dateModified': '',
'modifiedBy': '',
'active': true };
So what is the correct way to code this so my interface is initialised without having to hard code it?
test.component.html
<h1>
Edit Scenario - {{ name.value }}
</h1>
<div class='panel-body'>
<form novalidate
#scenarioForm="ngForm"
(ngSubmit)="saveForm()">
<div class='row'>
<div class='col-md-1'><label for="scenarioName" class="control-label">Name</label></div>
<div class='col-md-6'>
<div class="form-group" [class.has-error]="name.invalid && name.touched">
<input class="form-control"
#name="ngModel"
name="scenarioName"
type="text" maxlength="50" placeholder="Scenario name" required
[(ngModel)]="scenario.name">
</div>
</div>
</div>
<div class='row'>
<div class='col-md-1'><label for="descption">Descption</label></div>
<div class='col-md-6'>
<div class="form-group" [class.has-error]="description.invalid && description.touched">
<textarea #description="ngModel"
ngControl="description"
class="form-control"
rows="4" maxlength="500"
placeholder="What is this scenario for?"
required
name="description"
[(ngModel)]="scenario.description"></textarea>
<div *ngIf="description.invalid && description.dirty" class="alert alert-danger">
Description is required, and must be at least blah blah...
</div>
</div>
</div>
</div>
<div class='panel-footer'>
<button class="btn btn-default" type="submit">Save</button>
</div>
<br><br>
<input #id type="text" name="id" [(ngModel)]="scenario.id" #id="ngModel">
</form>
<div id="debugging">
<br>
Valid? {{scenarioForm.valid}}<br>
Model: {{ scenario | json }} <br>
Model: {{ result | json }}<br>
Form: {{ scenarioForm.value | json }}
<br>
</div>
test.component.ts
import { Component, OnInit, NgModule } from '#angular/core';
import { SharedModule } from './../shared/shared.module';
import { FormsModule, NgForm, FormGroup } from '#angular/forms';
import { CommonModule } from '#angular/common';
import { ScenarioService } from './scenario.service';
import { IScenario } from './scenario';
import { Router, ActivatedRoute } from '#angular/router';
#NgModule({
imports: [
FormsModule
],
exports: [FormsModule]
})
#Component({
selector: 'app-scenario-form',
templateUrl: './test.component.html',
})
export class TestComponent implements OnInit {
// tslint:disable-next-line:no-inferrable-types
private pageTitle: string = 'New Scenario';
scenario: IScenario;
result: IScenario;
constructor(private _scenarioService: ScenarioService,
private _route: ActivatedRoute) {}
ngOnInit() {
const scenarioId = this._route.snapshot.paramMap.get('id');
if (scenarioId) {
this._scenarioService.getScenario(+scenarioId).subscribe(
scenario => this.scenario = scenario);
}
}
}
Since you are getting the response from API asynchronously, you need to handle the null initially, you can do that with safe navigation operator
<input #id type="text" name="id" [(ngModel)]="scenario?.id" #id="ngModel">
same applies for all your fields.
As you mentioned above, other way is to fix via initializing the object Scenario
Since you need to accomplish two way data binding, you need to initialize the object by creating an instance
scenario: IScenarioUpdate = {modifiedBy: ''}
I have a simple template driven form like so:
HTML:
<div class="container">
<h1>Hero Form</h1>
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" [(ngModel)]="model.name" name="name" #name="ngModel">
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control" id="alterEgo" [(ngModel)]="model.alterEgo" name="alterEgo">
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" id="power" [(ngModel)]="model.power" name="power">
<option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
</select>
</div>
<button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
</form>
</div>
Component:
import { Component, OnInit } from '#angular/core';
import { Hero } from './hero';
#Component({
selector: 'at-hero',
templateUrl: './hero.component.html',
styleUrls: ['./hero.component.scss']
})
export class HeroComponent implements OnInit {
constructor() {
//
}
ngOnInit() {
//
}
powers = ['Really Smart', 'Super Flexible', 'Super Hot', 'Weather Changer'];
model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
submitted = false;
onSubmit() { this.submitted = true; }
newHero() {
this.model = new Hero(42, '', '');
}
}
How can I:
Reset the whole form from the component (not from the markup)?
Reset a single form field (e.g. the name field) also from the component and not the markup?
You can get the form by using ViewChild
Markup
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
...
</form>
Component
#ViewChild('heroForm') public heroForm: NgForm;
I suggest you also to look at Reactive Forms too. I think this will be more handy if you want to work with form in the typescript, not in the markup
I have a form which allows user to create an account, once the user clicks on submit button the user is navigated to another page with the details of that account they have create. The issue I am having is passing that object to the details view.
For example here is my component for the form,
import {Component, OnInit, OnDestroy, Input} from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import {Customer} from "../model/customer";
import {Router } from '#angular/router';
import {CustomerService} from "../service/customer.service";
import {CustomerProfileComponent} from "../customer-profile/customer-profile.component";
#Component({
selector: 'app-new-customer',
templateUrl: './new-customer.component.html',
styleUrls: ['./new-customer.component.css']
})
export class NewCustomerComponent implements OnInit {
#Input() customer: Customer;
//this is only dev data do not use this in prod
private countries = [];
private customerSources = [];
private secondarySources =[];
//Create the forum group
public newCustomerForm: FormGroup;
public submitted: boolean; // keep track on whether form is submitted
constructor(private fb: FormBuilder, public customerService: CustomerService,private router: Router) {
this.countries = [
{value:'UK'},
{value:'Germany'},
{value:'Turkey'},
{value:'Italy'}
];
this.customerSources = [
{value: 'Through a friend or colleague (not a Client)'},
{value: 'Through an existing Client'},
{value: 'Direct Sales (e.g. cold call, direct mail, email)'},
{value: 'Web Search e.g. Google'}
];
this.secondarySources = [
{value: '1st Hire'},
{value: 'A Test Client With A Long Business Name'},
{value: 'Abbigail West'}
];
}
ngOnInit() {
this.newCustomerForm = this.fb.group({
id:[''],
company_name: ['', [<any>Validators.required, <any>Validators.minLength(5)]],
vat:[''],
address:[''],
country:[''],
first_name:[''],
surname:[''],
phone:[''],
email:['',[<any>Validators.required, <any>Validators.minLength(5)]],
customer_sources:[''],
secondary_sources:['']
});
}
here is my form html,
<form [formGroup]="newCustomerForm" novalidate (ngSubmit)="saveNewCustomer(newCustomerForm.value, newCustomerForm.valid)">
<section>
<aside>
<p>Once you've added your new <b>Client</b>, you can come back and allow them access to view their <b>Invoices</b> & <b>Payments</b> - they can also make <b>Payments</b> via Paypal if you have it enabled.</p>
</aside>
<input type="hidden" name="id" formControlName="id"/>
<h4>New Client Details</h4>
<md-input-container>
<input mdInput type="text" name="company_name" placeholder="Customer Name" formControlName="company_name" />
<small [hidden]="newCustomerForm.controls.company_name.valid || (newCustomerForm.controls.company_name.pristine && !submitted)">
Customer Name is required (minimum 5 characters).
</small>
</md-input-container>
<md-input-container>
<input mdInput type="text" name="vat" placeholder="VAT Number" formControlName="vat"/>
</md-input-container>
<md-input-container>
<input mdInput type="text" name="address" placeholder="Address" formControlName="address" />
</md-input-container>
<md-select placeholder="Country" name="country" formControlName="country" >
<md-option *ngFor="let country of countries" [value]="country.value" >
{{country.value}}
</md-option>
</md-select>
<h4>Your Primary Contact</h4>
<div class="left-column">
<md-input-container>
<input mdInput type="text" name="first_name" placeholder="First Name" formControlName="first_name" />
</md-input-container>
</div>
<div class="left-column">
<md-input-container>
<input mdInput type="text" name="surname" placeholder="surname" formControlName="surname" />
</md-input-container>
</div>
<div class="clearfix"></div>
<div class="left-column">
<div class="left-column">
<md-input-container>
<input mdInput type="text" name="phone" placeholder="Phone" formControlName="phone"/>
</md-input-container>
</div>
</div>
<div class="right-column">
<div class="left-column">
<md-input-container>
<input mdInput type="text" name="email" placeholder="Email" formControlName="email"/>
<small [hidden]="newCustomerForm.controls.email.valid || (newCustomerForm.controls.email.pristine && !submitted)">
Email is required (minimum 5 characters).
</small>
</md-input-container>
</div>
</div>
<div class="clearfix"></div>
<h4>Customer Source</h4>
<div class="left-column">
<md-select placeholder="How were you introduced to this Client?" formControlName="customer_sources">
<md-option *ngFor="let cs of customerSources" [value]="cs.value" >
{{cs.value}}
</md-option>
</md-select>
</div>
<div class="right-column">
<md-select placeholder="Which Client introduced you?" formControlName="secondary_sources">
<md-option *ngFor="let ss of secondarySources" [value]="ss.value" >
{{ss.value}}
</md-option>
</md-select>
</div>
<div class="clearfix"></div>
</section>
<aside>
<div class="right-aside">
<button type="submit" class="cancel">Cancel</button>
<button type="submit" class="save">Save</button>
</div>
<div class="clearfix"></div>
</aside>
</form>
Customer service is in my app.module. Here I am saving the data and moving the user on to the new page.
saveNewCustomer(customer: Customer, isValid: boolean){
if(isValid){
this.submitted = true; // set form submit to true
this.customerService.saveNewCustomer(customer)
.subscribe(
res => this.customer,
error => console.log(<any>error)
);
this.router.navigateByUrl('/earning/customers/profile');
}
}
}
And this is the component I would like the customer object to use so it be present in the view.
import {Component, OnInit, Input} from '#angular/core';
import {Customer} from "../model/customer";
import {NewCustomerComponent} from "../new-customer/new-customer.component";
#Component({
selector: 'app-customer-profile',
templateUrl: './customer-profile.component.html',
styleUrls: ['./customer-profile.component.css'],
providers:[NewCustomerComponent]
})
export class CustomerProfileComponent implements OnInit {
#Input() customer: Customer;
constructor() {
console.log(this.customer);
}
ngOnInit() {
}
}
<main>
<header>
<h4> </h4>
<h1><strong><i></i>Customer profile</strong></h1>
</header>
<article>
<section>
<p></p>
<p>
{{customer}}
</p>
</section>
</article>
</main>
But customer is undefined in the CustomerProfileComponent. I am not sure what I am doing wrong. if anyone can point me in the right direction would be much appreciated.
Update to include service class based on suggestion
import { Injectable } from '#angular/core';
import {Http, Response, Headers, RequestOptions} from '#angular/http';
import {CookieService} from "angular2-cookie/services/cookies.service";
import {Observable, Subject} from "rxjs";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import {Customer} from "../model/customer";
#Injectable()
export class CustomerService {
private csrfToken;
private newCustomerUrl = "/earning/customers/new";
private customer = new Subject<Object>();
customer$ = this.customer.asObservable();
constructor(public http: Http, private cookieService: CookieService) {
this.getCsrfToken();
}
getCsrfToken(){
this.csrfToken = this.cookieService.get("PLAY_SESSION").substring(this.cookieService.get("PLAY_SESSION").indexOf("csrfToken"));
}
saveNewCustomer(customer:Customer): Observable<Customer>{
let headers = new Headers({
'Content-Type':'application/json'
});
let options = new RequestOptions({ headers: headers });
return this.http.post(this.newCustomerUrl+"?"+this.csrfToken, customer, options) // ...using post request
.map((res:Response) => res.json()) // ...and calling .json() on the response to return data
.catch(this.handleError); //...errors if any
}
private handleError (error: Response) {
return Observable.throw('Internal server error: ' + error);
}
emitCustomer(customer) {
this.customer.next(customer);
}
}
As mentioned a shared service would be a good option. the following example is sharing the Object between the components via service:
Your service:
public sharedCustomer = {};
And the component, after that you have received your customer from the api, push the customer to the service:
this.customerService.saveNewCustomer(customer)
.subscribe(res => {
this.customer = res;
this.customerService.sharedCustomer = this.customer;
this.router.navigateByUrl('/earning/customers/profile');
});
}
Notice that I'm emitting the customer inside the subscription, as well as the navigation, to ensure that the customer gets stored in the service properly, as well as not navigating away from page before that.
Then in your detail page:
ngOnInit() {
this.customer = this.customerService.sharedCustomer;
}