HTML Dropdown disappears after a value is set to null in Angular - html

I have two values power and mainPower which describe the same thing but mainPower saves an id of type Long in the backend and power saves all attributes of this dto. With the code below I can update a hero's power, meaning I can change it from something to the other and also make it null (meaning no power).
The problem is that when a power is set to null or it was null before, the dropdown menu disappears. I don't want the dropdown to disappear, I just want it to say "no power" when it is null and I should be able to change it from null to something.
hero-detail.component.html
<form #heroUpdateForm = "ngForm">
{{diagnostic}}
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name"
required
*ngIf="hero" [(ngModel)]="hero.name" name="name">
</div>
<div class="form-group">
<label for="desc">Description</label>
<input type="text" class="form-control" id="desc"
*ngIf="hero" [(ngModel)]="hero.desc" name="desc">
</div>
<div class="form-group" *ngIf="hero?.power">
<label for="power">Main Power</label>
<select class="form-control" id="power"
required
[(ngModel)]="hero.mainPower" name="power">
<option [ngValue]="null">no power</option>
<ng-container *ngFor="let power of powers">
<option [value]="power.id">{{power.name}}</option>
</ng-container>
</select>
</div>
</form>
hero-detail.component.ts
export class HeroDetailComponent implements OnInit {
public heroes: Hero[];
public powers: Power[];
submitted = false;
private hero: Hero;
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private powerService: PowerService,
private location: Location,
private router: Router
) {}
ngOnInit(): void {
this.getHero();
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.getHeroes();
this.getPowers();
}
getHero(): void{
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id).subscribe(
(hero: Hero) => {
this.hero = hero;
if (hero.power != null) {
this.powerService.getPowerById(hero.power).subscribe(
(powerResponse) => {
hero.power = powerResponse;
}
);
}
});
}
public getHeroes(): void {
this.heroService.getHeroes().subscribe(
(response: Hero[]) => {
this.hero = response;
},
(error: HttpErrorResponse) => {
alert(error.message);
}
);
}
public getPowers(): void {
this.powerService.getAllPowers().subscribe(
(response: Power[]) => {
this.power = response;
},
(error: HttpErrorResponse) => {
alert(error.message);
}
);
}
goBack(): void {
this.location.back();
}
save(): void {
this.heroService.updateHero(this.hero, this.hero.id)
.subscribe(() => this.goBack());
}
onSubmit() {this.submitted = true;}
get diagnostic() {return JSON.stringify(this.hero);}

Is obvious why it'll disappear, look at your template again:
<div class="form-group" *ngIf="hero?.power"> <!-- lookie here, if either hero or power is null this element along with everything in it will be gone!! -->
<label for="power">Main Power</label>
<select class="form-control" id="power" <!-- your select is inside the div above with the ng if -->
required
[(ngModel)]="hero.mainPower" name="power">
<option [ngValue]="null">no power</option>
<ng-container *ngFor="let power of powers">
<option [value]="power.id">{{power.name}}</option>
</ng-container>
</select>
essentially the ngIf you have in the div surrounding the select element says the following:
if(ngIf) hero and power -> display element
if(ngIf) no hero and power -> remove element
You might not want to have *ngIf="hero?.power" perhaps you should just check if there is a hero *ngIf="hero" instead

Related

React Dropdown Select Initialization and getting results back for key of displayed text

I am new to React.
In an effort to assign a CpyMasterID (MySql key) to a User I am using a React Dropdown Select. I can get it to display a list of Company keys (from the MySQL dbase).
What I need it to do is display the Company Name of the assigned company (if the userscpymasterId is not null) and return the associated cpymasterid if a Company Name is selected.
Bottom of CreateUserSmallComponent Rendering
Here is my existing code.
import React, { Component } from 'react'
import UserService from '../Services/UserService';
import CpyMasterService from '../Services/CpyMasterService';
class CreateUserSmallComponent extends Component {
constructor(props) {
super(props)
this.state = {
isLoading:true,
isReadOnly:false,
userId: this.props.match.params.userId,
usersfirstName: '',
userslastName: '',
userscpymasterId: '',
cpymaster: []
}
this.saveOrUpdateUser = this.saveOrUpdateUser.bind(this);
}
componentDidMount(){
CpyMasterService.getCpyMaster().then((res) => {
this.setState({ cpymaster: res.data});
});
if(this.state.userId === '_add'){
return
}else{
UserService.getUserById(this.state.userId).then( (res) =>{
let user = res.data;
this.setState({isLoading:false});
this.setState({usersfirstName: user.usersfirstName,
userslastName: user.userslastName,
userscpymasterId: user.userscpymasterId
});
});
}
}
saveOrUpdateUser = (e) => {
e.preventDefault();
let user = {usersfirstName: this.state.usersfirstName,
userslastName: this.state.userslastName,
userscpymasterId: this.state.userscpymasterId
};
if(this.state.userId === '_add'){
UserService.createUser(user).then(res =>{
this.props.history.push('/users');
});
}else{
UserService.updateUser(user, this.state.userId).then( res => {
this.props.history.push('/users');
});
}
}
changeFirstNameHandler= (event) => {
this.setState({usersfirstName: event.target.value});
}
changeLastNameHandler= (event) => {
this.setState({userslastName: event.target.value});
}
changeUsersCpyMasterIdHandler= (event) => {
this.setState({userscpymasterId: event.target.value});
}
cancel(){
this.props.history.push('/users');
}
getTitle(){
if(this.state.userId === '_add'){
return <h3 className="text-center">Add User</h3>
}else{
return <h3 className="text-center">Update User</h3>
}
}
render() {
return (
<div>
{this.state.isLoading &&
<h4>Getting Data....</h4>
}
<br></br>
<div className = "container">
<div className = "row">
<div className = "card col-md-6 offset-md-3 offset-md-3">
{
this.getTitle()
}
<div className = "card-body">
<form>
<div className = "form-group">
<label> First Name: </label>
<input placeholder="First Name" name="usersfirstName" className="form-control"
readOnly = {this.state.isReadOnly} value={this.state.usersfirstName} onChange={this.changeFirstNameHandler}/>
</div>
<div className = "form-group">
<label> Last Name: </label>
<input placeholder="Last Name" name="userslastName" className="form-control"
readOnly = {this.state.isReadOnly} value={this.state.userslastName} onChange={this.changeLastNameHandler}/>
</div>
<div>
<select>
<option selected disabled = "true" > -- Select CpyMaster -- </option>
{
this.state.cpymaster.map((x,y)=> (<option key={x}>{y}</option>))
}
</select>
</div>
<button className="btn btn-success" disabled={this.state.isReadOnly} onClick={this.saveOrUpdateUser}>Save</button>
<button className="btn btn-danger" onClick={this.cancel.bind(this)} style={{marginLeft: "10px"}}>Cancel</button>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default CreateUserSmallComponent
I thank all for input to help move me forward on this one.
Company Master Format
public class CpyMaster {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long cpymasterid;
#Column(name = "cpymastername")
private String cpymasterName;
#Column(name = "cpymasteraddress1")
private String cpymasterAddress1;
#Column(name = "cpymasteraddress2")
private String cpymasterAddress2;
#Column(name = "cpymastercity")
private String cpymasterCity;
#Column(name = "cpymasterzip")
private String cpymasterZip;
#Column(name = "cpymasterdocumentfolder")
private String cpymasterdocumentFolder;
#Column(name = "cpymastercreated")
private String cpymasterCreated;
#Column(name = "cpymasterupdated")
private String cpymasterUpdated;
#Column(name = "states_statesid")
private String states_statesId;
public long getCpymasterid() {
return cpymasterid;
}
Here is the code that works. Original post was selecting the key value for the cpymaster, this example is the same concept, but references the States array (1-Florida, 2=Georgia, etc.).
...
<select onChange={this.changeCpyMasterStateSelectHandler}
value={this.state.states_statesId} style={{marginLeft: "10px"}}>
<option selected disabled = "true" > -- Select State -- </option>
{
this.state.states.map((item,index)=> (<option key={index}
value={item.statesid}>{item.statesName} ))
}
</select>
...

Input fields as lists in Angular

I want to take inputs from the user and append them to a list I have in my typescript. Here's what I have tried so far.
Here's the code:
<input type="text" id="course" name="course" class="form-control"[(ngModel)]="institutes.course">
institutes={course:''}
Use a function to add a new course and trigger it using a button.
app.component.html :
<!-- Input field -->
<input type="text" id="course" name="course" class="form-control" [(ngModel)]="newCourse">
<!-- Add button -->
<button (click)="addCourse()">Add</button>
app.component.ts :
newCourse : string = '';
allCourses : string[] = [];
// Function to add course
addCourse(){
this.allCourses.push(this.newCourse);
//Reset input
this.newCourse = '';
}
Demo : https://stackblitz.com/edit/angular-hzh42b
Write the following method in your component -
constructor(private url: string, private http: HttpClient) {}
posts: any;
createPost(input: HTMLInputElement) {
const post = { title: input.value };
this.posts.splice(0, 0, post);
input.value = "";
this.service.create(post).subscribe(
newPost => {
post.id = newPost;
},
(error: AppError) => {
this.posts.splice(0, 1);
if (error instanceof BadInput) {
// this.form.setErrors(error.originalError);
} else {
throw error;
}
}
);
}
Include this method in your service -
constructor(private url: string, private http: HttpClient) {}
create(resource) {
return this.http.post(this.url, JSON.stringify(resource)).pipe(
map(response => response),
catchError(this.handleError)
);
}
write the following code in your HTML -
<input
(keyup.enter)="createPost(title)"
#title
type="text"
class="form-control"
/>
You are good to go now!
The following code will help you to add a course into a list, which will be displayed in the web page.
.ts
courseList contains a list of all the added courses
course is the current course that you are adding.
addCourse is a method which will add a course into the list, and clear the course string.
public courseList = [];
public course;
addCourse() {
this.courseList.push(this.course);
console.log(this.courseList);
this.course = '';
}
.html
There is an input field which will take in course name.
And an add course button which will add the entered course name into the list and display the course list in the web page.
<ul>
<li *ngFor="let course of courseList">
{{course}}
</li>
</ul>
<input type="text" id="course" name="course" class="form-control" [(ngModel)]="course">
<button (click)="addCourse()">Add Course</button>

Angular 6 reactive forms. FormArray of select

I followed Angular Reative Form guide that explains how to add a FormArray of Adrresses to a FormGroup.
Now I want to have a hero that can have different powers, selecting them from a select, or better from a dynamic array of select.
Passing from the example of Angular Docs to my desired functionality I can't make it to run.
This is my hero-form.ts
#Component({
selector: 'app-hero-form',
templateUrl: './hero-form.component.html',
styleUrls: ['./hero-form.component.css']
})
export class HeroFormComponent implements OnInit, OnChanges {
heroForm: FormGroup;
nameChangeLog: string[] = [];
hero: Hero = new Hero();
allPowers: Power[] = [];
constructor(private fb: FormBuilder, private powerService: PowerService) {
this.createForm();
this.logNameChange();
}
ngOnInit() {
this.powerService.getAll().subscribe(powers => this.allPowers = powers);
}
createForm() {
this.heroForm = this.fb.group({
name: ['', Validators.required],
powers: this.fb.array([]),
});
}
ngOnChanges() {
this.rebuildForm();
}
rebuildForm() {
this.heroForm.reset({
name: this.hero.name
});
this.setPowersControl(this.hero.powers);
}
setPowersControl(powers: Power[]) {
const powersFGs = powers.map(pow => this.fb.group(pow));
const powersFormArray = this.fb.array(powersFGs);
this.heroForm.setControl('powers', powersFormArray);
}
get powers(): FormArray {
const pows = this.heroForm.get('powers') as FormArray;
return pows;
}
addPowerChoice() {
this.powers.push(this.fb.control(new Power()));
// this.powers.push(this.fb.group(new Power(), Validators.required));
}
onSubmit() {
this.hero = this.prepareSaveHero();
console.log('SAVING HERO', this.hero);
// this.heroService.updateHero(this.hero).subscribe(/* error handling */);
this.rebuildForm();
}
prepareSaveHero(): Hero {
const formModel = this.heroForm.value;
// deep copy of form model lairs
const powersDeepCopy: Power[] = formModel.powers.map(
(pow: Power) => Object.assign({}, pow)
);
// return new `Hero` object containing a combination of original hero value(s)
// and deep copies of changed form model values
const saveHero: Hero = {
id: this.hero.id,
name: formModel.name as string,
// addresses: formModel.secretLairs // <-- bad!
powers: powersDeepCopy
};
return saveHero;
}
revert() { this.rebuildForm(); }
logNameChange() {
const nameControl = this.heroForm.get('name');
nameControl.valueChanges.forEach(
(value: string) => this.nameChangeLog.push(value)
);
}
}
This is my hero-form.html
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
<div style="margin-bottom: 1em">
<button type="submit" [disabled]="heroForm.pristine" class="btn btn-success">Save
</button>
<button type="button" (click)="revert()" [disabled]="heroForm.pristine" class="btn btn-danger">Revert</button>
</div>
<!-- Hero Detail Controls -->
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="powers" class="well well-lg">
<div *ngFor="let pow of powers.controls; let i=index" [formControlName]="i">
<!-- The repeated power template -->
<h4>Potere #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Power:
<select class="form-control">
<option *ngFor="let pow of allPowers" [value]="pow">{{pow.name}}</option>
</select>
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)="addPowerChoice()" type="button">Add a Power</button>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>
This is power-service that is only returning stubbed data
#Injectable({
providedIn: 'root'
})
export class PowerService {
constructor() {
}
getAll(): Observable<Power[]> {
return of(this.getProds());
}
getProds(): Power[] {
const powers = [];
for (let i = 0; i < 10; i++) {
const pow = new Power();
pow.id = i+'';
pow.name = 'Power ' + i;
powers.push(pow);
}
return powers;
}
}
And these are my data models
export class Hero {
id: number;
name: string;
powers: Power[];
}
export class Power {
id: string = '';
name: string = '';
}
Here I have make a stackblitz example but it's not working
I've solved
I have moved formControlName from div onto select as suggested by Lucas Klaassen, and changed [value] to [ngValue] onto option
<form [formGroup]="heroForm" (ngSubmit)="onSubmit()">
<div style="margin-bottom: 1em">
<button type="submit"
[disabled]="heroForm.pristine" class="btn btn-success">Save
</button>
<button type="button" (click)="revert()"
[disabled]="heroForm.pristine" class="btn btn-danger">Revert
</button>
</div>
<!-- Hero Detail Controls -->
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="powers" class="well well-lg">
<div *ngFor="let pow of powers.controls; let i=index">
<!-- The repeated power template -->
<h4>Potere #{{i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Power:
<select class="form-control" [formControlName]="i">
<option *ngFor="let pow of allPowers" [ngValue]="pow">{{pow.name}}</option>
</select>
</label>
</div>
</div>
<br>
<!-- End of the repeated address template -->
</div>
<button (click)="addPowerChoice()" type="button">Add a Power</button>
</div>
</form>
<p>heroForm value: {{ heroForm.value | json}}</p>
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>
Then I have changed onSubmit() adding a Hero's constructor call as follow
onSubmit() {
this.hero = this.prepareSaveHero();
console.log('SAVING HERO', this.hero);
// this.heroService.updateHero(this.hero).subscribe(/* error handling */);
this.hero = new Hero();
this.rebuildForm();
}
Then I have added a custom constructor to Hero class
export class Hero {
id: number;
name: string;
powers: Power[];
constructor() {
this.id = 0;
this.name = '';
this.powers = [];
}
}
Now it's working and correctly rebuilding form after submit

Angular2 Edit profile: Display retrieved data in HTML input tag

I have an edit profile page. So when the user clicks on the edit profile page the users details should be displayed in the input tag fields in the HTML. But I am unable to display the details I am retrieving.
edit-profile.component.html
<form novalidate (ngSubmit)="onFormSubmit(signupForm)" #signupForm="ngForm">
<div class="form-inputs clearfix">
<div class="row">
<div class="col-md-6">
<p>
<label class="required">First Name<span>*</span></label>
<input
type="text"
[value]="accountDetails.firstName"
name="firstName"
[(ngModel)] = "user.firstName"
#firstName = "ngModel"
required><p>{{accountDetails.firstName}} </p>
<span *ngIf="firstName.invalid && (firstName.dirty || firstName.touched)" class="input-error">
<span *ngIf = "firstName.errors?.required">
First Name field can't be blank
</span>
</span>
</p></form>
edit-profile.component.ts
import { Component, OnInit } from '#angular/core';
import { ApiServiceProvider } from '../services/api.service';
#Component({
selector: 'app-edit-profile',
templateUrl: './edit-profile.component.html',
styleUrls: ['./edit-profile.component.css']
})
export class EditProfileComponent implements OnInit {
public user: any = {};
public accountDetails: any = {}
constructor(
private api: ApiServiceProvider
) { }
ngOnInit() {
let profile = JSON.parse(localStorage.getItem("profile"));
this.accountDetails = profile.user;
console.log(this.accountDetails);
}
public onFormSubmit({ value, valid }: { value: any, valid: boolean }) {
this.user = value;
this.api.put("/users/editprofile", value.userId, false)
.subscribe((data) => {
console.log(data)
localStorage.setItem("profile", JSON.stringify(data));
location.href = "/profile"
}, (err) => {
alert("Registartion failed " + err);
})
}
}
you only populate your accountDetails variable but you try to bind your user variable.
so you have:
this.accountDetails = profile.user;
but you bind your input to the user variable:
<input type="text" [(ngModel)]="user.firstName" #firstName="ngModel" required>
Either populate your user variable:
this.user = profile.user;
or bind to your account details variable:
<input type="text" [(ngModel)]="accountDetails.firstName" #firstName="ngModel" required>
Also you need to close your div clearfix, div row and div column correctly before closing the form tag. </div></div></div></form>

Initialize input populated by ng-bootstrap datepicker to blank

I have two inputs, each populated by their own ng-bootstrap datepicker that pops up when I click a button. The input populates properly when you choose a new date, but my problem is if I want to initialize the inputs to null or simply another value. I know my getDate function gets in the way of that, I'm just not sure how to change it to allow that.
Screenshot (datepicker opens with click of the button to the right of each input, one per input):
HTML:
<form class="form-inline">
<div>
<div class="form-group" [ngClass]="{'has-error':!secondForm.controls['startDate'].valid && secondForm.controls['startDate'].touched}">
<label>Start Date:</label>
<input style="width:250px" [value]="getDate('start')" class="form-control" type="text" [formControl]="secondForm.controls['startDate']">
</div>
<div style="display:inline-block">
<ngb-datepicker id="special" *ngIf="startCheck;" [(ngModel)]="startDate" (ngModelChange)="showDatePick(0)" [ngModelOptions]="{standalone: true}"></ngb-datepicker>
</div>
<button type="button" class="btn icon-calendar" (click)="showDatePick(0)"></button>
<div class="form-group" [ngClass]="{'has-error':!secondForm.controls['endDate'].valid && secondForm.controls['endDate'].touched}">
<label>End Date:</label>
<input style="width:250px" [value]="getDate('end')" class="form-control" type="text" [formControl]="secondForm.controls['endDate']">
</div>
<div style="display:inline-block">
<ngb-datepicker id="special" *ngIf="endCheck;" [(ngModel)]="endDate" (ngModelChange)="showDatePick(1)" [ngModelOptions]="{standalone: true}"></ngb-datepicker>
</div>
<button type="button" class="btn icon-calendar" (click)="showDatePick(1)"></button>
<button type="submit" class="btn icon-search" [disabled]="!secondForm.valid"></button>
<span [hidden]="!secondForm.hasError('endDateLessThanStartDate')" class="alert alert-danger first">End Date must be equal to or after Start Date</span>
</div>
</form>
Typescript:
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import {NgbDateStruct} from '#ng-bootstrap/ng-bootstrap';
import {DatePipe} from "#angular/common";
#Component({
selector: 'calendar-pick',
styleUrls: ['../app.component.css'],
templateUrl: './calendarpick.component.html',
providers: [DatePipe]
})
export class CalendarPickComponent {
public dt: NgbDateStruct;
public dt2: NgbDateStruct;
public startCheck: boolean = false;
public endCheck: boolean = false;
secondForm : FormGroup;
public constructor(fb: FormBuilder, private datePipe: DatePipe) {
this.secondForm = fb.group({
'startDate' : [null, Validators.required],
'endDate' : [null, Validators.required]
}, {validator: this.endDateAfterOrEqualValidator})
}
public getDate(dateName: string) {
let workingDateName = dateName + 'Date';
let timestamp = this[workingDateName] != null ? new Date(this[workingDateName].year, this[workingDateName].month-1, this[workingDateName].day).getTime() : new Date().getTime();
this.secondForm.controls[dateName + 'Date'].setValue(this.datePipe.transform(timestamp, 'MM/dd/yyyy'));
}
public showDatePick(selector):void {
if(selector === 0) {
this.startCheck = !this.startCheck;
} else {
this.endCheck = !this.endCheck;
}
}
endDateAfterOrEqualValidator(formGroup): any {
var startDateTimestamp, endDateTimestamp;
for(var controlName in formGroup.controls) {
if (controlName.indexOf("startDate") !== -1) {
startDateTimestamp = Date.parse(formGroup.controls[controlName].value);
}
if (controlName.indexOf("endDate") !== -1) {
endDateTimestamp = Date.parse(formGroup.controls[controlName].value);
}
}
return (endDateTimestamp < startDateTimestamp) ? { endDateLessThanStartDate: true } : null;
}
}
Changed second line in getDate function to let timestamp = this[workingDateName] != null ? new Date(this[workingDateName].year, this[workingDateName].month - 1, this[workingDateName].day).getTime() : null;