In a simple form, I added an input, and a div for the error. Like this
<input
formControlName="username"
id="username"
type="text"
class="form-control">
<div *ngIf="username.touched && username.invalid" class="alert alert-danger">
<div *ngIf="username.errors.required" >Username is required</div>
<div *ngIf="username.errors.shouldBeUnique">Username is not unique</div>
</div>
I also created a class that holds the shouldBeUnique verifier, like so:
static shouldBeUnique(control: AbstractControl): Promise<ValidationErrors | null>
{
return new Promise((resolve, reject) => {
console.log('START');
setTimeout(() => {
console.log(control);
if((control.value as string) == 'asd')
resolve({ shouldBeUnique: true});
else
resolve(null);
}, 2000);
});
}
Here is the component.ts
export class SignupFormComponent {
form = new FormGroup({
username: new FormControl('',[
Validators.required,
UsernameValidators.shouldBeUnique
]),
password: new FormControl('', Validators.required),
});
get username(){
return this.form.get('username');
}
}
Now, the thing is, in the tutorial I'm looking, when the teacher types 'asd' in the username input field, after 2 seconds, the div with corresponding error is displayed.
When I type 'asd' in my field, the div with error is displayed immediately, and with no message. I just get a red div with no text whatsoever.
His and my code are the same, BUT he is using angular 4, and I'm using angular 6.
The purpose of this code is to simulate and explain the async calls. That's why this code makes no scence in real project. It's just a simulation.
Is there a difference between A4 and A6 that makes the difference in my case?
Why is my div for errors showing right when I change focus, rather then wait for 2s?
How can I get the result that he is getting?
I hope I provided enough info, im totaly new to angular.
Related
I am getting this error when I try to build a reactive form for creating a new password form.I have mentioned the source code below and when I remove the source code part then there is no error but without that my operation is not working as well. I think I have to add or delete something in my source code to get the desired output
main.ts:12 TypeError: Cannot read property 'value' of null
at FormGroup.passwordShouldMatch [as validator] (password.validators.ts:18)
at FormGroup._runValidator (forms.js:4089)
at FormGroup.updateValueAndValidity (forms.js:4050)
at new FormGroup (forms.js:4927)
at FormBuilder.group (forms.js:8924)
at new ChangePasswordComponent (change-password.component.ts:15)
at createClass (core.js:31987)
at createDirectiveInstance (core.js:31807)
at createViewNodes (core.js:44210)
at callViewAction (core.js:44660)
static passwordShouldMatch(control : AbstractControl) {
let newPassword = control.get('newPassowrd');
let confirmPassword = control.get('confirmPassowrd');
if (newPassword.value !== confirmPassword.value){
return { passwordShouldMatch:true };
return null;
}
}
As you didn't add any code snippet I am considering your form structure is something like this.
this.fb.group({
newPassowrd: [''],
confirmPassowrd: [''],
});
here, you include an custom validation function called passwordShouldMatch and this function looks fine. So, I assume that you did something wrong when you set the validator to that form group.
this.fb.group({
newPassowrd: [''],
confirmPassowrd: [''],
}, { validator: this.passwordShouldMatch});
this is how you should set the validation function for the form group. And in html your form should be something like this.
<form [formGroup]="form" novalidate (ngSubmit)="onSubmit(survey)">
<input type="text" placeholder="Untitled form" formControlName="newPassowrd">
<input type="text" placeholder="Untitled form" formControlName="confirmPassowrd">
<span *ngIf="form.hasError('passwordShouldMatch')">not match</span>
</form>
everything should work this way. Here is the working version of stackblitz
I'm using Meteor with React. I have a really simple goal, but i have tried a lot and can't solve it for myself. I will show you my attemps below.
I want to create a form for the Ingredients. At the first moment there is only one input (for only one ingredient) and 2 buttons: Add Ingredient and Submit.
class IngredientForm extends Component {
render() {
return(
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text"/>
{ this.renderOtherInputs() }
<input type="button" value="Add Ingredient" onClick={this.addIngredient.bind(this)}>
<input type="submit" value="Submit Form">
</form>
);
}
}
So when I click Submit then all the data goes to the collection. When I click Add Ingredient then the another text input appears (in the place where renderOtherInputs() ).
I know, that Meteor is reactive - so no need to render something directly. I should underlie on the reactive data storage.
And I know from the tutorials the only one way to render something - I should have an array (that was based on collection, which is always reactive) and then render something for each element of that array.
So I should have an array with number of elements = number of additional inputs. that is local, so I can't use Collection, let's use Reactive Var instead of it.
numOfIngredients = new ReactiveVar([]);
And when I click Add button - the new element should be pushed to this array:
addIngredient(e) {
e.preventDefault();
let newNumOfIngredients = numOfIngredients.get();
newNumOfIngredients.push('lalal');
numOfIngredients.set(newNumOfIngredients);
}
And after all I should render additional inputs (on the assumption of how many elements I have in the array):
renderOtherInputs() {
return numOfIngredients.get().map((elem) => {
return(
<input type="text"/>
);
}
}
The idea is: when I click Add button then new element is pushed to the ReactiveVar (newNumOfIngredients). In the html code I call this.renderOtherInputs(), which return html for the as many inputs as elements I have in my ReactiveVar (newNumOfIngredients). newNumOfIngredients is a reactive storage of data - so when I push element to it, all things that depends on it should re-render. I have no idea why that is not working and how to do this.
Thank you for your help.
Finally I got the solution. But why you guys don't help newbie in web? It is really simple question for experienced developers. I read that meteor and especially react have powerful communities, but...
the answer is: we should use state!
first let's define our state object in the constructor of react component:
constructor(props) {
super(props);
this.state = {
inputs: [],
}
}
then we need a function to render inputs underlying our state.inputs:
renderOtherInputs() {
return this.state.inputs.map( (each, index) => {
return (
<input key={ index } type="text" />
);
});
}
and to add an input:
addInput(e) {
e.preventDefault();
var temp = this.state.inputs;
temp.push('no matter');
this.setState({
inputs: temp,
});
}
p.s. and to delete each input:
deleteIngredient(e) {
e.preventDefault();
let index = e.target.getAttribute('id');
let temp = this.state.inputs;
delete temp[index];
this.setState({
inputs: temp,
});
}
I am using Meteor with React. Consider this simple component below. There is a local mini-Mongo collection CommentsCollection. The component will insert a row in it when componentWillMount will be called. getMeteorData will return the first record in the collection and we'll be able to modify the title. Problem: if I place my cursor at the start of the title and start typing, after the first character update the cursor will jump to the end of the string and the rest of my typing will be placed there. How do I work around this?
CommentsCollection = new Meteor.Collection(null); // Local mini-mongo collection
EventTestComponent = React.createClass({
mixins : [ReactMeteorData],
componentWillMount(){
CommentsCollection.insert({title:"test title", message:"some test message"});
},
getMeteorData(){
return {
comment: CommentsCollection.findOne()
};
},
handleTitleChange(e){
CommentsCollection.update({_id: this.data.comment._id}, {$set:{title: e.target.value}});
},
render(){
if(this.data.comment) {
return (
<div>
<input type="text" value={this.data.comment.title} onChange={this.handleTitleChange}/>
</div>
);
}else{
return <div>Loading...</div>
}
}
});
I came up with this solution right after I posted the question:
<input type="text"
defaultValue={this.data.comment.title}
onKeyUp={this.handleTitleChange}/>
So: change value to defaultValue, and onChange to onKeyUp. Works like a charm!
I have two separate form input's (both text type), one in template A, and one in template B. Template A invokes template B. All the specific names/properties of these two input form's are unique. I have event handlers for both, within their own properly named Template.name.events().
When I build a very simple test case of this, no problems, everything works fine. But in my larger and more complex actual app, when I enter text into the template B form, the correct template B submit event handler gets invoked. And then...the template A submit event handler gets invoked! This happens even when I do nothing but an event.PreventDefault call in handler B (side question: are event handlers ever invoked for reactive reasons, or strictly "event occurred" reasons?). I am able to work around this odd behavior for the moment by checking in the A event handler for an undefined name property and just exiting if that's the case, but that's just a kludge for something wrong somewhere. Any suggestions as to a likely culrprit for this odd behavior in my code? Thanks!
Here's the code for the two templates in the failing case; the first (entryHall, with the "new-room" form input) is the "A" template, the second (knock, with the "knock-room" form input) the "B" template. Underneath the event handling code for those two templates is the html+handlebars code for the template definitions and invocations. Sorry for the verbosity and lack of a simpler failing case!
Template.entryHall.events({
// NEW ROOM REQUEST PROCESSING
"submit.new-room": function (event) {
event.preventDefault();
if (event.target.roomName === undefined) {
console.log ("in submit new room and roomname in event is undefined");
return;
}
var rName = event.target.roomName.value;
// Is there already a room name of this same name in the Rooms collection?
var roomsCursor = Rooms.findOne({ roomName: rName });
if (roomsCursor != null) {
// It's a dup; don't allow it
event.target.roomName.value = "Duplicate room name, try again";
return(null);
}
var uName = Session.get ('userName');
// It's a unique name, put it in the Rooms collection.
Rooms.insert({
roomName : rName,
owner : uName,
members: [], // an array of user names
knockRequests: [], // an array of user names
chat : null,
files : null
});
// We have the room document added to the Rooms collection, now we have to
// add the room to the owned list for the user
var userEntry = PZUsers.find({ userName : uName }).fetch();
PZUsers.update({ _id : userEntry[0]._id},
{ $push: { ownedRooms: rName }});
ownedRoomCount++;
roomReactor.changed();
event.target.roomName.value = "";
}
});
Template.knock.events({
// Knock on a room request processing
"submit.knock-room": function (event) {
// Prevent default browser form submit
event.preventDefault();
var knockName = event.target.knockName.value;
event.target.knockName.value = "";
console.log("in knock room submit!");
// Can only knock on a room that exists!
var knockRoomCursor = Rooms.findOne({ roomName: knockName });
if (knockRoomCursor == null) {
console.log ("no such room found to knock on");
return;
}
// Add a knock request to this room, and add this room the the user's list of "open knocks" rooms
var roomEntry = Rooms.find({ roomName : knockName }).fetch();
console.log ("_id of room: " + knockName + " is: " + roomEntry[0]._id);
Rooms.update({ _id : roomEntry[0]._id },
{ $push: { knockRequests: Session.get('userName') }});
roomReactor.changed();
}
});
And here's the invoking html:
<template name="entryHall">
<h2>Welcome {{userName}}</h2>
<h3>Create a new room:</h3>
<div class="roomName">
<form class="new-room">
<input type="text" name="roomName" id="roomName" placeholder="Select a room name" />
</form>
</div>
{{markNoOwnedRooms}}
{{#each ownedRooms}}
{{#if firstOwnedRoom}}
<h3>Enter one of your own rooms:</h3>
{{/if}}
{{ > room }}
{{/each}}
{{markNoMemberRooms}}
{{#each memberRooms}}
{{#if firstMemberRoom}}
<h3>Enter one of your member rooms:</h3>
{{/if}}
{{ > room }}
{{/each}}
<h3>Knock to request entry:</h3>
{{ > knock }}
</template>
<template name="room">
<li>{{this}}</li>
</template>
<template name="knock">
<div class="knockName">
<form class="knock-room">
<input type="text" name="knockName" id="knockName" placeholder="Enter room name" />
</form>
</div>
</template>
I am working on a little project with knockout where i have to simulate the answer to asome questions.
I dynamically build 3 buttons and when i click one i compare the text on it with the correct answer and if it's correct it should become green.., if it's not correct it should become red and the right one should become green..
I was trying to use the binding to css property of knockout, but it seems that the property is not correctly refreshed, even if the computed associated with the css changes
The html:
<ul data-bind="foreach: chosenQuestionAnswers">
<li>
<button data-bind="text: answerText, css: $root.answerStatus(), click: $root.selectButton"></button>
</li>
</ul>
The ko part:
selectedAnswer: ko.observable(),
isAnswerCorrect: ko.observable(),
selectButton: function (value) { my.AppViewModel.isAnswerCorrect(my.AppViewModel.checkIfCorrectAnswer(value.answerText));
},
checkIfCorrectAnswer: function (value) {
return (value == my.AppViewModel.chosenQuestionCorrectAnswer());
},
my.AppViewModel.answerStatus = ko.computed(function () {
var exit = this.isAnswerCorrect() == true ? "highlightRight" : "highlightWrong";
console.log(this.isAnswerCorrect());
console.log(exit);
return exit;
}, my.AppViewModel);
The console.log shows the correct value of the var exit... but the css of the button does not change...
Any idea why?..
Thanks.... I.
you're referencing an old version of KO (2.1), the latest as of today is version is 2.2.1. Your fiddle works when referencing the new version # http://knockoutjs.com/downloads/knockout-2.2.1.js. Here's a fork: http://jsfiddle.net/drdamour/xe2M5/
The dynamic css binding (how you're using it) was only added in 2.2 see http://blog.stevensanderson.com/2012/10/29/knockout-2-2-0-released/
stack won't let me submit without some code so...here some worthless code:
var x = {a:1}
css classes are part of the view, try to keep them out of the ViewModel, it should focus on business logic.
I would do something like
http://jsfiddle.net/hLXbq/
HTML
<button data-bind="css: { valid: valid, invalid: invalid }, click: toggle">Toggle</button>
JS
ViewModel = function () {
this.valid = ko.observable(false);
this.invalid = ko.computed(function () {
return !this.valid();
}, this);
};