Angular 2 Dynamic Form - Implement Clear Functionality - javascript

I'm trying to implement Clear functionality for Angualr 2 dynamic forms, which should clear all the values to null. But i'm getting an error when i try to use this.questions[i].value="null"; or this.questions[i].value="undefinded"; or this.questions[i].value=''; to replace each value in the form.
Working Code: http://plnkr.co/edit/SL949g1hQQrnRUr1XXqt?p=preview
Typescript class code:
import { Component, Input, OnInit } from '#angular/core';
import { FormGroup, REACTIVE_FORM_DIRECTIVES } from '#angular/forms';
import { QuestionBase } from './question-base';
import { QuestionControlService } from './question-control.service';
#Component({
selector: 'dynamic-form',
templateUrl: 'app/dynamic-form.component.html',
directives: [REACTIVE_FORM_DIRECTIVES],
providers: [QuestionControlService]
})
export class DynamicFormComponent implements OnInit {
#Input() questions: QuestionBase<any>[] = [];
form: FormGroup;
payLoad:object;
questiont: QuestionBase<any>;
constructor(private qcs: QuestionControlService) { }
ngOnInit() {
this.form = this.qcs.toFormGroup(this.questions);
console.log("Form Init",this.questions);
this.questiont = JSON.parse(JSON.stringify(this.questions));
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.value);
this.payLoad2=this.payLoad;
this.questiont = JSON.parse(JSON.stringify(this.questions));
}
cancel(){
console.log("Canceled");
this.questions = JSON.parse(JSON.stringify(this.questiont));
}
clear(){
for(var i=0;i<this.questions.length;i++){
this.questions[i].value='';
}
console.log("Cleared");
}
}
HTML code:
<div>
<form [formGroup]="form">
<div *ngFor="let question of questions" class="form-row">
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<input *ngSwitchCase="'textbox'" [formControlName]="question.key"
[id]="question.key" [type]="question.type" [(ngModel)]="question.value">
<select [id]="question.key" [(ngModel)]="question.value" *ngSwitchCase="'dropdown'" [formControlName]="question.key" >
<option *ngFor="let opt of question.options" [ngValue]="opt.key" >{{opt.value}}</option>
</select>
</div>
<div class="errorMessage" *ngIf="!form.controls[question.key].valid">{{question.label}} is required</div>
</div>
<div class="form-row">
<button type="submit" [disabled]="!form.valid" (click)="onSubmit()">Save</button>
<button type="button" class="btn btn-default" (click)="cancel()">Cancel</button>
<button type="button" class="btn btn-default" (click)="clear()">Clear</button>
</div>
</form>
<div *ngIf="payLoad" class="form-row">
<strong>Saved the following values</strong><br>{{payLoad}}
</div>
</div>
Is there any way that can be done without getting error.

The problem is with your logic to check and display a message if a field is required.
Your plunker code was throwing the exception Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'
The problem is that it runs over your template code with values for questions[i].value but then it changes once you call clear. Angular compares questions[i].value and notices it's different then when it started rendering and thinks it made a mistake. It didn't pick up that you changed the value yet. You have to let it know the value was changed.
If you were to run the code with prod enabled, it wouldn't run this safety check and you'd get no error, but don't enable prod mode just to fix this.
Solution
The fix is import ChangeDetectorRef from #angular/core to dynamic.form.component.ts
Add private cdr: ChangeDetectorRef to the constructor
and add this.cdr.detectChanges(); to the end of your clear() method.
This will let know angular pick up that changes have happened and it won't think it made a mistake
P.S. the lines that are culprits are
<div class="errorMessage" *ngIf=" form && !form.controls[question.key].valid">{{question.label}} is required</div>
and
group[question.key] = question.required ? new FormControl(question.value || '', Validators.required) : new FormControl(question.value || ''); });

Related

How to access the properties of a formArray in HTML?

I'm trying to implement a reactive Angular form, but, I can't access the properties of the array on HTML, I never worked with reactive form, if anyone could guide me I would be grateful! I'm using Angular 10 and I have the following code:
TS
operationModel: IScriptOperationsModel;
formOperation: FormGroup;
constructor(
private fb: FormBuilder,
...
) {}
ngOnInit() {
this.operationModel = new IScriptOperationsModel();
this.operationModel.scriptOperationOrders = [];
this.buildForm(new IScriptOperationsModel());
this.scriptOperationsService.findScriptOperation(this.operationId).subscribe((operation) => {
this.operationModel = operation.data as IScriptOperationsModel; // api return
this.buildForm(this.operationModel); // I pass the return of the api to the form
});
}
buildForm(operation: IScriptOperationsModel) {
this.formOperation = this.fb.group({
descriptionCode: [operation.descriptionCode],
description: [operation.description],
workStations: this.fb.array([])
});
this.formOperation.setControl('workStations', this.fb.array(this.operationModel.scriptOperationOrders));
}
get workStations(): FormArray {
return this.formOperation.get('workStations') as FormArray;
}
HTML
<div
class="card"
[ngClass]="{'bg-principal': idx === 0, 'bg-alternative': idx !== 0}"
formArrayName="workStations"
*ngFor="let workstation of workStations.controls; index as idx"
>
<div class="card-body" [formGroupName]="idx">
<div class="form-row">
<div class="form-group col-md-1">
<label>Id Oper.</label>
<input
type="text"
name="idOperation"
class="form-control"
disabled
formControlName="rank" <!-- whatever with or without binding gives error -->
/>
</div>
<div class="form-group col-md-2">
<label>Time</label>
<input
type="time" class="form-control" name="defaultTime"
[formControlName]="defaultTime" <!-- whatever with or without binding gives error -->
/>
</div>
</div>
</div>
</div>
Models
export class IScriptOperationsModel extends Serializable {
public description: string;
public descriptionCode: string;
public scriptOperationOrders: IScriptOperationOrdersModel[]; // array which I need
}
export class IScriptOperationOrdersModel extends Serializable {
public rank: number;
public defaultTime: string;
public asset?: IAssetModel;
public provider?: IProviderModel;
}
error-handler.service.ts:87 Error: Cannot find control with path: 'workStations -> 0 -> rank' # undefined:undefined
NOTE: I already looked at some answers here on the site such as this, this and this , but none of them solved this problem!
your problem is here :
this.formOperation.setControl(
'workStations',
this.fb.array(this.operationModel.scriptOperationOrders) <== here the problem
);
you are passing an array of IScriptOperationOrdersModel instead of array of form group.
To make your code working, you have to loop on every element of this.operationModel.scriptOperationOrders array , and instanciate a new FormControl object then push it in the workStations form array.
To access its elements, you can use controls[indexOfGroup].rate
You can take a look at this simple example you will understand everything.

How to bind to an Angular form from users selected option

OK it's a bit more complicated than the headline..
This form I am working on is a form group. It has a few fields ( supplement name, description and tags) the supplement name one is what I need help with as I have not worked on a complicated form like this and want to get it right and not just offer a messy patch job.
Here is the expected logical order of what happens
user adds a new supplement by clicking on the field and begins typing "creatine" for example
there is a query sent out that fetches products based on the entry into the input and
returns a JSON that are offered as suggestions
user clicks the suggestion "creatine"
field is populated and binds
we add another entry through the "add option" and repeat for X amount of products we want to
add.
What actually happens
user adds new supplement by clicking the field and types "creatine" suggestion request is
sent off and populates the dropdown
user clicks on the suggestion "creatine" the field takes that value
value is actually blank
user adds another supplement but the previous selection is in the field
user clears it and types again
value is blank
What needs to happen is the user can add X amount of supplements and able to grab whatever option from the dropdown recommendation and it is added to the form group array and does not interfere with the other form group array values.
I know this is not the right way to bind the form and I don't think it's right the way i'm binding the mat input field to trigger the query and this is the reason why I'm asking the question again, to not offer a patch job.
Component code
import { Subscription } from 'rxjs/Subscription';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { UtilitiesService } from '../../utilities/utilities.service';
import { GetSupplementsService } from './get-supplements.service';
#Component({
selector: 'app-supplements',
templateUrl: './supplements.component.html',
styleUrls: ['./supplements.component.css'],
providers: [GetSupplementsService],
})
export class SupplementsComponent implements OnInit {
supplementForm: FormGroup;
queryField: FormControl = new FormControl();
private supplementInventoryResults: Array<ISupplementInventoryResponse>;
private eventForm: FormGroup;
private searchResults: any;
private searchSubscription: Subscription;
private addSupplementSubscription: Subscription;
subcription: Subscription;
constructor (
private bottomSheet: MatBottomSheet,
private _fb: FormBuilder,
private ref: ChangeDetectorRef,
private _utils: UtilitiesService,
private getSupplements: GetSupplementsService,
private router: Router
) { }
public ngOnInit(): void {
this.browsingStackHistory = false;
this.loading = true;
this.supplementForm = this._fb.group({ // the form in question
entryArray: this._fb.array([
this.getUnit()
])
});
this.searchSubscription =
this.queryField.valueChanges
.debounceTime(600)
.distinctUntilChanged()
.switchMap((query) => this.getSupplements.search_supplement_by_category(query))
.subscribe((result) => {
this.searchResults = result;
});
}
public ngOnDestroy(): void {
this.subcription.unsubscribe();
}
private getUnit(): FormGroup {
return this._fb.group({
supplementName: [''],
description: [''],
tags: ['']
});
}
private addUnit(): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.push(this.getUnit());
}
private removeUnit(i: number): void {
const control = <FormArray>this.supplementForm.controls['entryArray'];
control.removeAt(i);
}
private addSupplement(): void { // this will do the post to the service
const supplementObject = {
start: this._utils.get_upload_time(),
data: this.supplementForm.value,
rating: 0
};
}
}
Template
[![<mat-tab label="Add Stack (Test)">
<div style="padding:8px;">
<div fxLayout="row wrap">
<div fxFlex.gt-sm="50%" fxFlex="100">
<h1>Add New Supplements Stack</h1>
<form \[formGroup\]="supplementForm" class="log-workout">
<!-- Start form units array with first row must and dynamically add more -->
<div fxLayout="column" fxLayoutAlign="center center" class="row-height">
<div formArrayName="entryArray">
<mat-divider></mat-divider>
<!-- loop throught units -->
<div *ngFor="let reps of supplementForm.controls.entryArray.controls; let i=index">
<!-- row divider show for every nex row exclude if first row -->
<mat-divider *ngIf="supplementForm.controls.entryArray.controls.length > 1 && i > 0"></mat-divider>
<br>
<!-- group name in this case row index -->
<div \[formGroupName\]="i">
<!-- unit name input field -->
<div class="row">
<mat-form-field class="example-form">
<input matInput placeholder="Supplement Name" \[formControl\]="addSupplementField"
formControlName="supplementName" \[matAutocomplete\]="auto">
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let product of supplementResults" \[value\]="product?.product_name">
<img class="example-option-img" aria-hidden \[src\]="product?.product_image" height="25">
{{product?.product_name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Description" formControlName="description" required>
</mat-form-field>
<mat-form-field class="example-form">
<input matInput placeholder="Tags" formControlName="tags" required>
</mat-form-field>
</div>
<!-- row delete button, hidden if there is just one row -->
<button mat-mini-fab color="warn" *ngIf="supplementForm.controls.entryArray.controls.length > 1"
(click)="removeUnit(i)">
<mat-icon>delete forever</mat-icon>
</button>
</div>
</div>
<!-- New unit button -->
<mat-divider></mat-divider>
<mat-card-actions>
<button mat-raised-button (click)="addUnit()">
<mat-icon>add box</mat-icon>
Add Other Product
</button>
</mat-card-actions>
<button mat-raised-button (click)="addSupplement()">
<mat-icon>add box</mat-icon>
Add Supplement
</button>
</div>
</div>
<!-- End form units array -->
</form>
</div>
</div>
</div>][1]][1]
Having the below when the getUnit() function is called apparently binds it in the sense it will operate independently and without conflicts.
private getUnit(): FormGroup {
const formGroup = this._fb.group({
supplementName: [''],
review: [''],
rating: [''],
notes: [''],
tags: ['']
});
formGroup.get('supplementName').valueChanges
.debounceTime(300)
.distinctUntilChanged()
.switchMap((search) => this.getSupplements.search_supplement_by_category(search))
.subscribe((products) => {
this.supplementResults = products;
});
return formGroup;
}

Angular: Making Button Toggle Group required for Material Stepper

I'm relatively new to Angular and am just getting to grips with the Material Design Components and how they work and can interact with each other.
I'm building a multi-step web application, so it seemed logical to use the Material Stepper. The user will also be able to choose between various options on these steps, so I've used the Button Toggle Group to present these options.
What I'd like to be able to do - and am wondering if it is possible - is to make the Button Toggle Group a "required" field for the stepper. On some instances, the toggle group will be the only option on the step and I'd like to make it so that the user can't move onto the next step without selecting a Toggle Group option.
I've tried simple solutions like adding "required" onto the mat-button-toggle, like so:
<mat-button-toggle (click)="getOptionName(Oname);" class="btn btn-primary step-button" id="{{step1option.id}}" [ngClass]="status ? 'selected' : ''" required><span #Oname>{{step1option.text}}</span></mat-button-toggle>
And onto the mat-button-toggle-group:
<mat-button-toggle-group required>
But, unfortunately it's not that simple, which I didn't expect it to be. But hey, you might as well start with the most simple and obvious solution first, right?
Obviously, it didn't work and my knowledge of Angular isn't that extensive at the moment and trying various searches didn't turn up anything helpful (and/or anything I could understand).
Here's my code so far:
My component.html:
<mat-horizontal-stepper [linear]="isLinear" #stepper>
<mat-step label="Step 1" [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<div ng-controller="step1">
<h2>I'm thinking of...</h2>
<mat-button-toggle-group>
<div *ngFor="let step1option of step1options">
<mat-button-toggle (click)="getOptionName(Oname);" class="btn btn-primary step-button" id="{{step1option.id}}" [ngClass]="status ? 'selected' : ''"><span #Oname>{{step1option.text}}</span></mat-button-toggle>
</div>
</mat-button-toggle-group>
<div>You chose <strong>{{ selectedStep1Option }}!</strong></div>
<button mat-stroked-button matStepperNext class="btn btn-secondary continue-btn">Continue</button>
</div>
</form>
</mat-step>
<mat-step label="Step 2" [stepControl]="secondFormGroup">
<form [formGroup]="secondFormGroup">
<p>Test</p>
</form>
</mat-step>
<mat-step label="Step 3" [stepControl]="thirdFormGroup">
<form [formGroup]="thirdFormGroup">
<p>Test</p>
</form>
</mat-step>
</mat-horizontal-stepper>
And my component.ts:
import { Component, OnInit } from '#angular/core';
import { TimeoutError } from 'rxjs';
import {FormBuilder, FormGroup, Validators} from '#angular/forms';
#Component({
selector: 'app-customer-portal-step1',
templateUrl: './customer-portal-step1.component.html',
styleUrls: ['./customer-portal-step1.component.scss']
})
export class CustomerPortalStepOneComponent implements OnInit {
isLinear = true;
firstFormGroup: FormGroup;
secondFormGroup: FormGroup;
thirdFormGroup: FormGroup;
constructor(private _formBuilder: FormBuilder) { }
ngOnInit() {
this.firstFormGroup = this._formBuilder.group({
firstCtrl: ['', Validators.required]
});
this.secondFormGroup = this._formBuilder.group({
secondCtrl: ['', Validators.required]
});
this.thirdFormGroup = this._formBuilder.group({
secondCtrl: ['', Validators.required]
});
}
selectedStep1Option: string = 'nothing yet';
step1options = [
{
text: 'Buying my first home',
id: 'buying'
},
{
text: 'Moving to a new home',
id: 'moving'
},
{
text: 'Remortgaging my home',
id: 'remortgage'
},
{
text: 'Purchasing a buy-to-let',
id: 'purchase'
}
];
status: boolean = false;
getOptionName (Oname: any) {
this.selectedStep1Option = Oname.textContent;
localStorage.setItem('I\'m thinking of:', JSON.stringify({ motivation: Oname.textContent }));
}
}
I guess what I'm asking is, is this even possible, am I on the right track and, if not, what's the best approach for me to get the result I'm hoping for?
Any help you can offer would be appreciated.
You can use attribute disabled along with a dynamic expression to disable the matStepperNext, preventing the user from moving forward in the stepper. It looks like your default value is 'nothing yet'. You can disable the button for example by checking if the value is still the default/invalid 'nothing yet':
<mat-button-toggle-group>
<div *ngFor="let step1option of step1options">
<mat-button-toggle (click)="getOptionName(Oname);" class="btn btn-primary step-button" id="{{step1option.id}}" [ngClass]="status ? 'selected' : ''"><span #Oname>{{step1option.text}}</span></mat-button-toggle>
</div>
</mat-button-toggle-group>
<div>You chose <strong>{{ selectedStep1Option }}!</strong></div>
<button mat-stroked-button matStepperNext class="btn btn-secondary continue-btn" [disabled]="selectedStep1Option === 'nothing yet'">Continue</button>
Here is an example in action showing disabling a button based on a mat-button-toggle-group value.
Hopefully that helps!

Angular .removeAt(i) at FormArray does not update in DOM - Angular

I thought it was an issue with my implementation but seems that my code for creating dynamic FormArray should be functional based from this question I raised. When I integrate it to my project, the remove function does delete the element from the FormArray, but it does not reflect in the interface/ does not remove object from the DOM. What could be causing this?
import {
Component,
VERSION
} from '#angular/core';
import {
FormGroup,
FormControl,
FormArray,
Validators,
FormBuilder
} from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
objectProps: any[];
public dataObject = [{
"label": "Name",
"name": "name",
"type": "text",
"data": ""
},
{
"label": "Contacts",
"name": "contacts",
"type": "inputarray",
"array": []
}
]
form: FormGroup;
constructor(private _fb: FormBuilder) {}
ngOnInit() {
const formGroup = {};
for (let field of this.dataObject) {
if (field.type == "inputarray") {
console.log("THIS IS " + field.type)
formGroup[field.name] = this._fb.array([''])
} else {
console.log("THIS IS " + field.type)
formGroup[field.name] = new FormControl(field.data || '') //, this.mapValidators(field.validation));
}
}
this.form = new FormGroup(formGroup);
}
addFormInput(field) {
const form = new FormControl('');
( < FormArray > this.form.controls[field]).push(form);
}
removeFormInput(field, i) {
( < FormArray > this.form.controls[field]).removeAt(i);
}
}
<form *ngIf="form" novalidate (ngSubmit)="onSubmit(form.value)" [formGroup]="form">
<div *ngFor="let field of dataObject">
<h4>{{field.label}}</h4>
<div [ngSwitch]="field.type">
<input *ngSwitchCase="'text'" [formControlName]="field.name" [id]="field.name" [type]="field.type" class="form-control">
<div *ngSwitchCase="'inputarray'">
<div formArrayName="{{field.name}}" [id]="field.name">
<div *ngFor="let item of form.get(field.name).controls; let i = index;" class="array-line">
<div>
<input class="form-control" [formControlName]="i" [placeholder]="i">
</div>
<div>
<button id="btn-remove" type="button" class="btn" (click)="removeFormInput(field.name, i)">x</button>
</div>
</div>
</div>
<div>
<button id="btn-add" type="button" class="btn" (click)="addFormInput(field.name)">Add</button>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-danger btn-block" style="float: right; width:180px" [disabled]="!form.valid">Save</button>
This is not a great solution but I solved my problem by manipulating value and after removing the control.
I simply moved the item that I wanted to remove to the end of the array and then I removed the last item.
removeItem(index: number): void {
const value = this.formArray.value;
this.formArray.setValue(
value.slice(0, index).concat(
value.slice(index + 1),
).concat(value[index]),
);
this.formArray.removeAt(value.length - 1);
}
I hope it helps someone struggling with this issue in the future.
Maybe you can try to force change detection using the reference to application. To do that inject the ApplicationRef in the constructor an call the tick(); on your removeFormInput method.
constructor(private _fb: FormBuilder, private appRef: ApplicationRef) {}
And in removeFormInput
removeFormInput(field, i) {
(<FormArray>this.form.controls[field]).removeAt(i);
this.appRef.tick();
}
Take a look at angular documentation: API > #angular/core /ApplicationRef.tick()
replace below function, you are not removing the row object from 'dataObject'.
removeFormInput(field, i) {
( < FormArray > this.form.controls[field]).removeAt(i);
this.dataObject.splice(this.dataObject.indexOf(field),1);
}
Take a look here Add and Remove form items I build on stackblitz, for me its working fine, adding and removing items... Take a look.
working version
I was facing this problem as well. The solution for me was to get rid of/fix the trackBy function in NgFor*. I think you need to introduce a proper trackBy function and it might solve your error.
sauce: How to use `trackBy` with `ngFor`

MEAN app with angular 2 error: Cannot read property _id of undefined

I am building an application for the first time to store, update, view and delete Client profiles. I followed the Angular tour of heroes to build the basic app and then pieced together the mongodb and express portions from around the net.
I am getting this error in my browser console when i attempt to delete a client profile -
ERROR TypeError: Cannot read property '_id' of undefined
at ClientProfileComp.webpackJsonp.../../../../../src/app/components/clientProfile.component.ts.ClientProfileComp.delete
(clientProfile.component.ts:53)... (etc).
I have confirmed using postman that my express routing is working as intended. I am able to get all/create clients at /api/clients, as well as get, put and delete from /api/clients/:_id (where _id is the autogenerated id for each entry).
I believe the problem is in one of my component files, as the error only occurs when I attempt to delete or view specific client detail, which causes another type of error entirely (CastError). The problem likely began when I attempted to remove all mentions of clientProfile: ClientProfile[]; (or Hero in the case of the tutorial) as I am no longer importing the details from client.ts (hero.ts) since I am using a mongoose schema instead, and I do not believe I should be importing that schema into my front-end angular.
here is the delete section of clientProfile.service.ts:
delete(_id: number): Promise<void> {
const url = `${this.clientProfilesUrl}/${_id}`;
return this.http.delete(url, {headers: this.headers}).toPromise()
.then(() => null).catch(this.handleError);
}
and here is clientProfile.component.ts as requested (the most likely source of my problem being that i replaced all instances of clientProfile: ClientProfile; with clientProfile: any; without knowing what I was doing)
note the commented out import statement.
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
//import { ClientProfile } from '../old/clientProfile';
import { ClientProfileService } from '../services/clientProfile.service';
#Component({
selector: 'app-clientprofile',
templateUrl: '../views/clientProfile.component.html',
styleUrls: [ '../styles/clientprofile.component.css' ]
})
export class ClientProfileComp implements OnInit {
selectedClientProfile: any;
clientProfiles: any = [];
clientProfile: any;
constructor(
private clientProfileService: ClientProfileService,
private router: Router
) { }
gotoDetail(): void {
this.router.navigate(['/detail', this.selectedClientProfile._id]);
}
getClientProfiles(): void {
this.clientProfileService.getClientProfiles().then(clientProfiles => {
this.clientProfiles = clientProfiles;
});
}
ngOnInit(): void {
this.getClientProfiles();
}
onSelect(clientProfile: any): void {
this.selectedClientProfile = clientProfile;
}
add(name: string, address: string): void {
name = name.trim();
address = address.trim();
if (!name) { return; }
this.clientProfileService.create(name, address).then(clientProfile => {
this.clientProfiles.push(clientProfile);
this.selectedClientProfile = null;
this.getClientProfiles();
});
}
delete(clientProfile: any): void {
this.clientProfileService.delete(clientProfile._id).then(() => {
this.clientProfiles = this.clientProfiles.filter(h => h !==
clientProfile);
if (this.selectedClientProfile === clientProfile) { this.selectedClientProfile = null; }
});
}
}
I have been poring over this all day, and have read a lot of similar posts here too - but most of the solutions don't seem to apply to this case. If anyone could point me in the right direction, i'd be really grateful. if any more code is needed to explain what i'm trying to do i will gladly post it.
Based on the error message it seems that the error is here:
this.router.navigate(['/detail', this.selectedClientProfile._id])
It appears that you only set it in onSelect and there are several places in your code that you are setting this.selectedClientProfile to null. That would be the best place to look.
If you'd like to create a plunker that demonstrates your issue, we could look at it further.
As a side note, you are using promises instead of the now more common Observables. If you want to change over to using Observables, I have a complete example of CRUD (create, read, update, and delete) operations here: https://github.com/DeborahK/Angular2-ReactiveForms in the APM folder.
Found out one problem - my delete button in the html file was in a div that only appeared when a client was selected, instead of next to each client. this was a result of a half-finished measure i took to ensure users don't just click delete willy-nilly on each client.
Code before:
<h2>Client Profiles</h2>
<div class="add-client">
<label>Add new client</label>
<input placeholder="Client Name (required)" #clientProfileName />
<input placeholder="Client Address" #clientProfileAddress />
<button (click)="add(clientProfileName.value, clientProfileAddress.value);
clientProfileName.value=''; clientProfileAddress.value=''">
Add</button>
</div>
<ul class="clientProfiles">
<li *ngFor="let clientProfile of clientProfiles"
[class.selected]="clientProfile === selectedClientProfile"
(click)="onSelect(clientProfile)">
<span class="badge">{{clientProfile.idnumber}}</span>
<span>{{clientProfile.name}}</span>
</li>
</ul>
<div *ngIf="selectedClientProfile">
<h2>
{{selectedClientProfile.name | uppercase}} selected
</h2>
<button (click)="gotoDetail()">View Details</button>
<button class="delete"
(click)="delete(clientProfile);
$event.stopPropagation()">Delete</button>
//delete button will only appear when a client is selected
</div>
Code now:
<h2>Client Profiles</h2>
<div class="add-client">
<label>Add new client</label>
<input placeholder="Client Name (required)" #clientProfileName />
<input placeholder="Client Address" #clientProfileAddress />
<button (click)="add(clientProfileName.value, clientProfileAddress.value);
clientProfileName.value=''; clientProfileAddress.value=''">
Add</button>
</div>
<ul class="clientProfiles">
<li *ngFor="let clientProfile of clientProfiles"
[class.selected]="clientProfile === selectedClientProfile"
(click)="onSelect(clientProfile)">
<span class="badge">{{clientProfile.idnumber}}</span>
<span>{{clientProfile.name}}</span>
<button class="delete"
(click)="delete(clientProfile);
$event.stopPropagation()">Delete</button>
//delete button appears next to each client
</li>
</ul>
<div *ngIf="selectedClientProfile">
<h2>
{{selectedClientProfile.name | uppercase}} selected
</h2>
<button (click)="gotoDetail()">View Details</button>
</div>

Categories

Resources