I am building an account information page. I want to allow edit and save the changes (ie: name), but i get the same old name back. My code is as follows
<div >
<mat-form-field class="simple-form-field-50" *ngIf="isEditEnable">
<input matInput placeholder="Name" [(ngModel)]="name">
</mat-form-field>
<span *ngIf="!isEditEnable">Name : {{user?.givenName}} </span>
<button style="border:0;" *ngIf="!isEditEnable" (click)="onEdit()"><span><mat-icon style="font-size:16px;" matSuffix>create</mat-icon></span></button>
<button *ngIf="isEditEnable" mat-raised-button color="primary" (click)="onEdit()">Submit</button>
</div>
TS code:
export class ProfileComponent implements OnInit {
user: User;
constructor(
private http: HttpClient,
) { }
isEditEnable = false;
ngOnInit() {
this.http.get<User>('/api/user/details', {})
.subscribe((user) => {
this.user = user;
});
}
onEdit() {
this.isEditEnable = !this.isEditEnable;
}
}
Code Ouput:
[1] (https://imgur.com/nxiExeH.png)
After clicking edit button:
[2] (https://imgur.com/599SIF4.png)
After clicking submit, it gives old name back without changing
[3] (https://imgur.com/nxiExeH.png)
Ok, it looks like you never update the name property on the user, as well as remember to update the user in database (for this you'll need to add an endpoint)
<div >
<mat-form-field class="simple-form-field-50" *ngIf="isEditEnable">
<input matInput placeholder="Name" [(ngModel)]="name">
</mat-form-field>
<span *ngIf="!isEditEnable">Name : {{user?.givenName}} </span>
<button style="border:0;" *ngIf="!isEditEnable" (click)="onEdit()">
<span><mat-icon style="font-size:16px;" matSuffix>create</mat-icon></span>
</button>
<button *ngIf="isEditEnable" mat-raised-button color="primary" (click)="onEdit()">
Submit
</button>
</div>
export class ProfileComponent implements OnInit {
user: User;
name: string;
constructor(
private http: HttpClient,
) { }
isEditEnable = false;
ngOnInit() {
this.http.get<User>('/api/user/details', {})
.subscribe((user) => {
this.user = user;
});
}
// assume you have or will add an endpoint to handle user update
updateUser(user) {
this.http.put((`/api/user/${this.user.id}`, this.user))
}
onEdit() {
if (this.isEditEnable && this.user.givenName !== this.name) {
// assign new name to the user
this.user.givenName = this.name;
// use api update the user in your database
this.updateUser()
}
this.isEditEnable = !this.isEditEnable
}
}
The following goes to backend portion (assume you use node with express maybe?)
Your router probably has file that is named users.js, there you have a get endpoint defined as following
this.router.get('details', someFunctionThatGetsUser);
You will need to add another one
this.router.put('/:id', functionToUpdateUserInDB);
and add a function in your user service that updates it in DB
Related
I am trying to implement a reactive angular form where either A or B has to be entered. A is a unique id and B is a set of values which identify the id. Now I try to validate a Form that is valid if either A is entered or B is entered including all the required values. I found several solutions that implement this behavior based on FormFields but was not able to get it working with the group of values.
<form class="container" [formGroup]="myForm" (ngSubmit)="onSubmit()">
<mat-form-field class="w-1/2">
<mat-label>ID</mat-label>
<input matInput type="number" formControlName="id">
</mat-form-field>
<div class="grid grid-cols-3 gap-4" formGroupName="innerGroup">
<mat-form-field>
<mat-label>First Name</mat-label>
<input matInput type="number" formControlName="firstName">
</mat-form-field>
<mat-form-field>
<mat-label>Last Name</mat-label>
<input matInput type="number" formControlName="lastName">
</mat-form-field>
</div>
</form>
My first idea was to override the default validator for the form but I could not figure out how to do that. Not even sure if it would be possible. I was trying to adjust https://stackoverflow.com/a/48714721 to work in my scenario but I had no idea how to get it to work because of the additional complexity with the inner form group.
Using angular 14 I was able to produce a similar result to what you are describing I am not sure it will 100% solve your issue however it might help.
Basically what I did was create a validator function that is to be applied at the group level. This validator will check the valid state of any given controls be they a FormGroup or a FormControl. However, this alone will not solve the problem as if you have a form group angular will see that any underling control or group that is invalid will also invalidate the parent. So what I did was call .disable() on any control that was being checked by the validator function. This will disable the UI element and disable validation checking by angular allowing the parent to be considered valid when one of the children is valid but the other is invalid effectively creating a one and only one validator.
My specific example I was trying to get the OnlyOneValidator to work for a MatStepper.
Validator
export function onlyOneValidator(controlKeys: string[]) {
return (control: AbstractControl): ValidationErrors | null => {
let countOfValidControls = 0;
for (let key of controlKeys) {
const controlToCheck = control.get(key);
if (controlToCheck === null || controlToCheck === undefined) {
throw new Error(`Error: Invalid control key specified key was ${key}`);
}
countOfValidControls += controlToCheck?.valid ? 1 : 0;
}
if (countOfValidControls !== 1) {
// the count is not exactly one
return {
onlyOneValid: {
actualValidCount: countOfValidControls,
expectedValidCount: 1
}
};
}
return null;
};
}
Controller
#Component({
selector: "app-equipment-creation-page",
templateUrl: "./equipment-creation-page.component.html",
styleUrls: ["./equipment-creation-page.component.scss"],
})
export class EquipmentCreationPageComponent implements OnInit, OnDestroy {
public categories = [null, "Tools", "Vehicles"];
constructor(private _formBuilder: FormBuilder) {}
public categoryInformationGroup = this._formBuilder.group({
existingCategory: this._formBuilder.group({
category: new FormControl(null, [ Validators.required ])
}),
newCategory: this._formBuilder.group({
name: new FormControl("", [Validators.required]),
description: new FormControl("", [Validators.required])
})
}, {
validators: [
onlyOneValidator(["existingCategory", "newCategory"])
],
});
public ngOnDestroy(): void {
this.subscriptions.forEach(sub => {
sub.unsubscribe();
});
}
private subscriptions: Subscription[] = [];
public ngOnInit(): void {
this.subscriptions.push(this.categoryInformationGroup.controls.existingCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.newCategory.disable();
} else {
this.categoryInformationGroup.controls.newCategory.enable();
}
})
).subscribe());
this.subscriptions.push(this.categoryInformationGroup.controls.newCategory.statusChanges.pipe(
tap((status: string) => {
if (status === "VALID") {
this.categoryInformationGroup.controls.existingCategory.disable();
} else {
this.categoryInformationGroup.controls.existingCategory.enable();
}
})
).subscribe());
}
}
Template
<form [formGroup]="categoryInformationGroup.controls.existingCategory">
<mat-form-field>
<mat-label>Apply to existing category?</mat-label>
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ category ?? "None" }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
OR
<form [formGroup]="categoryInformationGroup.controls.newCategory">
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="name" placeholder="Name">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.name.hasError('required')">This field
is required
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Create New Category</mat-label>
<input matInput formControlName="description" placeholder="Description">
<mat-error *ngIf="categoryInformationGroup.controls.newCategory.controls.description.hasError('required')">
This field is required
</mat-error>
</mat-form-field>
</form>
Hopefully this helps or at least gives you some ideas about how to approach this. If anyone else has any thoughts on this please let me know I would love to find a better way to do this.
I have a form in a seperate component which looks like this:
<form [formGroup]="form">
<div class="card" style="margin-bottom: 10px" >
<input
formControlName="name"
type="text"
placeholder="please enter"
/>
</div>
</form>
the ts file looks like this (formcontrol part)
form = this.builder.group({
name: new FormControl(),
})
this is how I call it from the other component:
component B:
<form></form>
What I want to achieve now is the following I am filling some values from my backend which works, I know want to asign these values to my form from the form component. I know I have to use #Input I am kinda stuck against a wall and dont know how to connect them. Can someone give me a small example how I can fill my form from values from component b?
To my knowledge I have posted the most important part of this question, if anything else is needed feel free to ask
UPDATE_
Form.ts
#Input()
myObject: { name: string };
form = this.builder.group({
name: new FormControl()
});
ngOnChanges(changes: SimpleChanges): void {
if (changes.myObject?.currentValue) {
this.form.patchValue(this.myObject);
}
}
UPDATE2:
my form ts looks like this now:
#Input()
myObject: { name: string };
form = this.builder.group({
name: new FormControl()
});
ngOnChanges(changes: SimpleChanges): void {
if (changes.myObject && changes.myObject.currentValue) {
this.form.patchValue(this.myObject);
}
}
My form.hml like this:
<form [formGroup]="form">
<div class="card" style="margin-bottom: 10px" >
<input
formControlName="name"
type="text"
placeholder="please enter"
/>
</div>
</form
my calling component ts like this:
this.form.controls.legitimiertePersonNameField.setValue(response.vorname)
this.someName = this.form.controls.legitimiertePersonNameField.value -> gets the data from formcontrol in parent/calling
html from caling looks like this:
<gwg-form [myObject]="someName"></gwg-form>
The someName value is correct I could see it in a console.log but it does not set the value for the fomrCOntrol of the form.html it is just empty input
form.component.ts
#Component({
selector: 'my-form'
...
})
export class FormComponent implements OnChanges {
#Input()
myObject: { name: string };
form = this.builder.group({
name: new FormControl()
});
ngOnChanges(changes: SimpleChanges): void {
if (changes.myObject?.currentValue) {
this.form.patchValue(this.myObject);
}
}
}
form.component.html
<form [formGroup]="form">
<div class="card" style="margin-bottom: 10px" >
<input
formControlName="name"
type="text"
placeholder="please enter" />
</div>
</form>
calling.component.ts
#Component({ ... })
export class CallingComponent {
someObject: { name: string };
loadData(): void {
this.someObject = ...
}
}
calling.component.html
<my-form [myObject]="someObject">
</my-form>
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;
}
I would simply like to delete an item on click, I made a code but I have error, I've been stuck on it for 2 days.
ERROR TypeError: this.addedBook.indexOf is not a function
I have already asked the question on the site we closed it for lack of information yet I am clear and precise
Thank you for your help
service
export class BookService {
url: string = 'http://henri-potier.xebia.fr/books';
public booktype: BookType[];
item: any = [];
constructor(private http: HttpClient) { }
getBookList(): Observable<BookType[]> {
return this.http.get<BookType[]>(this.url);
}
addToBook() {
this.item.push(this.booktype);
}
}
addToBook() here for add book but i dont know how to use it to display added books in my ts file
ts.file
export class PaymentComponent implements OnInit {
addedBook: any = [];
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.addedBook = this.bookService.getBookList();
}
delete() {
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
}
}
html
<div class="product" *ngFor="let book of addedBook | async">
<div class="product-image">
<img [src]="book.cover" alt="book">
</div>
<div class="product-details">
<div class="product-title">{{book.title}}</div>
</div>
<div class="product-price">{{book.price | currency: 'EUR'}}</div>
<div class="product-quantity">
<input type="number" value="1" min="1">
</div>
<div class="product-removal">
<button class="remove-product" (click)="delete()">
Supprimé
</button>
</div>
interface
export interface BookType {
title: string;
price: number;
cover: string;
synopsis: string;
}
I think this.bookService.getBookList() returns Observable so for you case it is not the best solution use async pipe. You should simply subscribe to your server response and than asign it to your variable. and after deleting item only rerender your ngFor.
JS
export class PaymentComponent implements OnInit {
addedBook: any[] = [];
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
// Observable
this.bookService.getBookList().subscribe(response =>{
this.addedBook = response;
});
// Promise
/*
this.bookService.getBookList().then(response=>{
this.addedBook = response;
})*/
}
delete(){
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
// rerender your array
this.addedBook = [...this.addedBook];
}
}
HTML
<div class="product" *ngFor="let book of addedBook">
<div class="product-image">
<img [src]="book.cover" alt="book">
</div>
<div class="product-details">
<div class="product-title">{{book.title}}</div>
</div>
<div class="product-price">{{book.price | currency: 'EUR'}}</div>
<div class="product-quantity">
<input type="number" value="1" min="1">
</div>
<div class="product-removal">
<button class="remove-product" (click)="delete()">
Supprimé
</button>
</div>
UPDATE
I built a special stackblitz so you can see it in action
here is the link;
you can't use javascript splice on Observable stream, it is not an Array.
to be able to remove an item from a stream you need to combine it (the stream) with another stream (in your case) the id of the item you want to remove.
so first create 2 streams
// the $ sign at the end of the variable name is just an indication that this variable is an observable stream
bookList$: Observable<any[]>; // holds bookList stream
deleteBook$ = new Subject<{ id: string }>(); // holds book id stream
now pass the results you get from your database (which is an observable stream) to bookList$ stream you just created like that
ngOnInit(): void {
this.bookList$ = this.bookService.getBookList().pipe(
delay(0)
);
}
change your html template to that.. and pipe the results from database like that
<div class="product" *ngFor="let book of (bookList$ | sync)">
...
// make sure you include your`remove-product` button inside `*ngFor` loop so you can pass the `book id` you want to remove to the `delete()` function.
<button class="remove-product" (click)="delete(book)">
Supprimé
</button>
</div>
now back to your ts file where we gonna remove the item from the STREAM by modifying the Array and return a new stream.
bookList$: Observable<any[]>; // holds bookList stream
deleteBook$ = new Subject<{ id: string }>(); // holds book id stream
ngOnInit(): void {
this.bookList$ = this.this.bookService.getBookList().pipe(
delay(0)
);
combineLatest([
this.bookList$,
this.deleteBook$
]).pipe(
take1(),
map(([bookList, deleteBook]) => {
if (deleteBook) {
var index = bookList.findIndex((book: any) => book.id === deleteBook.id);
if (index >= 0) {
bookList.splice(index, 1);
}
return bookList;
}
else {
return bookList.concat(deleteBook);
}
})
).subscribe();
}
now all is left to do is remove the item
delete(book: any) {
this.deleteBook$.next({ id: book.id }); pass the book you want to remove to the stream, `combineLatest` will take care of the rest
}
if you make an exit please don't forget me :)
good luck!
From your code, we can see that getBookList() return an Observable. As addedBook is not a array reference it will won't have array methods. That is the cause for your issue.
If you want to do some operations from the service data, subscribe to the observable and store the reference of the value to addedBook.
export class PaymentComponent implements OnInit {
...
ngOnInit(): void {
this.bookService.getBookList().subscribe(
res => { this.addedBook = res }
);
}
...
}
And you need to remove the async keyword from your html
Typescript is mainly used to identify these kind of issues in compile time. The reason it doesn't throw error on compile time is that you've specified addedBook as any. While declaring you declare it as array and onInit you change it to observable, which can be avoided if you've specified type[] ex: string[]
I would suggest something like this
Service file
export class BookService {
url: string = 'http://henri-potier.xebia.fr/books';
//add an observable here
private bookUpdated = new Subject<bookType>();
public booktype: BookType[] = [];//initializa empty array
item: any = [];
constructor(private http: HttpClient) { }
//Ive changet the get method like this
getBookList(){
this.http.get<bookType>(url).subscribe((response) =>{
this.bookType.push(response);//Here you add the server response into the array
//here you can console log to check eg: console.log(this.bookType);
//next you need to use the spread operator
this.bookUpdated.next([...this.bookType]);
});
}
bookUpdateListener() {
return this.bookUpdated.asObservable();//You can subscribe to this in you TS file
}
}
Now in your TS file you should subscribe to the update listener. This is typically done in NgOnInit
Something like this:
export class PaymentComponent implements OnInit {
addedBook: BookType;
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.bookService.bookUpdateListener().subscribe((response)=>{
this.addedBook = response;//this will happen every time the service class
//updates the book
});
//Here you can call the get book method
this.bookService.getBookList();
}
delete() {
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
}
}
Essentially what happens is you are subscribed to when books get changed or updated. Now you can simply use addedBook.title or whatever you want in your HTML.
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>