Angular Reactive form asynchronous operation - asynchronous

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

Related

How To Update Database Using Angular Material Reactive Form And Restful API

Let Me take famous Book Example To explain My question.
I have a angular material reactive form build based on Book Model in my BookService.ts. When I change some field In this form and Submit it to my back end to update according record using Angular HttpClient PUT method It is not updating my database and comes back with and error. When I debug it, It shows ID is not defined, When I console.log(BookForm.value) I get This out put: {$key: 1234, Title: "Book Title1", Topic: "Topic1"} , No need to say my Angular HttpClient PUT Restful API needs that ID in order to be able to update that particular record in My Database table. Bellow Is My Simplified Mocked Code To Explain It.
BookModel.ts File, My Model
export interface Book{
ID: number;
Title: string;
Topic: string;
}
BookService.ts File, My Service
BookForm: FormGroup = new FormGroup({
$key: new FormControl(null),
Title: new FormControl('', Validators.required),
Topic: new FormControl('', Validators.required),
});
UpdateBook(bookObj: Book): Observable<Book>
{
return this.http.put<Book>(`...api/book/${bookObj.ID}`, bookObj,{
headers: new HttpHeaders({
'Content-Type: 'application/json'
})
})
}
Note: This Throw Error, ID Undefined
Book-form.component.html File
<form [formGroup] = "BookService.BookForm" class="FormCls">
<mat-grid-list rowHeight="200px">
<mat-grid-tile>
<div class="form-controles-container">
<input type="hidden" formControlName="$key" />
<mat-form-field>
<input formControlName="Title" matInput placeholder="Title*" />
<mat-error>Title Needed</mat-error>
</mat-form-field>
<mat-form-field>
<input formControlName="Topic" matInput placeholder="Topic*" />
<mat-error>Topic Needed</mat-error>
</mat-form-field>
<div class="button-row">
<button mat-raised-button color="primary" type="submit" (click)="onSubmit()">Submit</button>
</div>
</div>
</mat-grid-tile>
</mat-grid-list>
</form>
Book-form.component.ts File
onSubmit(): void
{
BookService.UpdateBook(BookService.BookForm.value).subscribe(
b => alert(`Book Updated Successfully`),
err => alert(`Exception While Updating: ${err}`)
);
}
For sure I know I need to some how convert my form value to my Book model and make sure I have that ID inside that before I pass it to my http put service. but I dont know how to do that, Im fairly new in both Angular and Typescript world and I am learning. I love reading before I ask, so went thru lots of Articles but none worked for me. For Example I tried below Article on stackoverfelow but did not work for me
Reactive Forms correctly convert Form Value to Model Object
I really Appreciate you professionals and Thank For Your Time And Help.
when you call any AbstractControl's .value method, you are gettin an object with prop: value pairs for each input in your form. As you can see, you will get an IDless object, maybe the reason is because the form has no 'ID' property, or maybe it is because the value is null (when you transform an object to JSON, all properties that have null value get deleted).
If you know the id from the HTML, you can pass it in the function
<button mat-raised-button color="primary" type="submit" (click)="onSubmit(sth.id)">
if that's not the case (I think it isn't), you could add the ID before calling your service with the .patchValue({ id: idInfo }) method that any AbstractControl has (in this case, you won't need to add a hidden input, unless you add it for another reason).
Do a patch before calling the service and it should work.
I'm not sure if this is enough info to help you, feel free to ask any questions.
edit (to add a code example):
onSubmit(): void
{
// the lines I added
const bf = BookService.BookForm;
bf.patchValue({ ID: 'anyIdYouLike' });
// what stays the same (except the bf.value shortened version)
BookService.UpdateBook(bf.value).subscribe(
b => alert(`Book Updated Successfully`),
err => alert(`Exception While Updating: ${err}`)
);
}

creating element on click in Meteor

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,
});
}

Updating model value onChange in Meteor + React places cursor at the end of the string

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!

Use Flow Router Param in Autoform

Friends,
I'm working on my first app in Meteor and hitting my head against the wall on something...
I have a scenario similar to a blog + comments situation where I have one collection (call it 'posts') and want to associate documents from another collection (call it 'comments').
The best way I know to pass the post._id to the comments as a "postId" field is to use the Flow Router params, since the form is on the 'post/:id' view.
But for the life of me, I cannot figure out how to get "var postId = FlowRouter.getParam('postId');" to pass to Autoform so it populates. I've tried adding it as a function in the schema, as a hook, and as a hidden field in the form on the page (obviously don't want to go that route).
Autoform is amazing and I want to use it, but may have to wire it up the hard way if I can't get this darn value to populate.
Any ideas? I've been hitting my head against the wall on this for a couple of days now.
Thanks!
First, just so we're on the same page, if you have your route is set up like this:
FlowRouter.route('/blog/:postId', {
action: function (params, queryParams) {
FlowLayout.render('layout', { body: 'postTemplate' });
},
});
You are able to call FlowRouter.getParam('postId') from inside the AutoForm hook
You'll need to use an AutoForm hook and have a complete schema. I'm using the package aldeed:collection2 for the schema set up. The postId field must be explicity declared. This code is running on both server and client.
Comments = new Mongo.Collection("comments");
Comments.attachSchema(new SimpleSchema({
comment: {
type: String,
label: "Comment"
},
postId: {
type: String
}
}));
Setting your form up like this is not what you want:
{{> quickForm collection="Comments" id="commentForm" type="insert"}}
That's no good because it will show the postId field in the HTML output. We don't want that, so you have to fully define the form like this:
{{#autoForm collection="Comments" id="commentForm" type="insert"}}
<fieldset>
{{> afQuickField name='comment' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Insert</button>
{{/autoForm}}
Then add the AutoForm hook. This code is running on the client.
var commentHooks = {
before: {
insert: function(doc){
var postId = FlowRouter.getParam('postId');
doc.postId = postId;
return doc;
}
}
};
AutoForm.addHooks(['commentForm'],commentHooks);
Make sure you have your allow/deny rules set up, and it should be working fine.
I was struggling with this same use case as well, and I found this on the Meteor forums: https://forums.meteor.com/t/use-flow-router-param-in-autoform/14433/2
If you're using a schema to build your form (either with the autoform or quickform tags) then you can put it right in there.
For example:
campaignId: {
type: String,
autoform: {
value: function() {
return FlowRouter.getParam('campaignId');
},
type: "hidden"
}
},

Meteor with Typeahead: where to call typeahead() on input element?

I'm using Meteor with Iron Router, and can't seem to get typeahead (this version: https://github.com/bassjobsen/Bootstrap-3-Typeahead) to work.
Here's some code:
HomeController = RouteController.extend({
//....
after: function () {
var tags = this.getData().tags;
console.log(tags);
if(tags.length > 0) {
var tags = ['hello', 'world'];
console.log("Adding typeahead for tags to ", $('.input-search')[0]);
console.log("tags: ", tags);
$('.input-search').typeahead({
source: tags,
updater: function(item) {
Router.go('/projects/tag/' + item);
}
});
}
},
I have a header that's part of the application layout, and has an input like this:
<input type="text" class="form-control input-search" data-provide="typeahead" placeholder="Search">
The jQuery in the after: function gets the input correctly. But calling typeahead on the input doesn't seem to activate typeahead properly: when typing in the input, nothing happens.
However if I wrap the typeahead call in a setTimeout, it does work.
Of course, whenever you start wrapping things in setTimeouts, something isn't right.
Where/when is the correct place to initialise typeahead when using Iron Router?
You can initialize the typeahead in the rendered function of your template. For instance:
Template.mytemplate.rendered = function() {
$('.input-search').typeahead({
source: tags,
updater: function(item) {
Router.go('/projects/tag/' + item);
}
});
};
I figured it out.
You need to make sure any inputs used by plugins are "preserved", i.e. not re-rendered, by Meteor. The simplest way to do this is to make sure the input has an ID attribute. So changing my search input to this fixed it:
<input type="text" id="input-search" class="form-control" data-provide="typeahead" placeholder="Search">
Relevant documentation: http://docs.meteor.com/#template_preserve
As of Meteor 1.0.3.1 and iron:router#1.0.7 working solution is to install sergeyt:typeahead and init typeahead like this:
Template.MyTemplate.rendered = function () {
Meteor.typeahead.inject();
}
Init may be done only once for top-level template.
I wrote an article about this, you can read it here.
In summary, your implementation using javascript to select and alter the element is bad practice in a reactive environment. I've tried it that way and it's a huge pain.
What I found is that you can create a helper that returns a JSON string of your typeahead data and use the data-source attribute, like so
JS
Template.myHelper.helper({
typeahead : function(){
return JSON.stringify(Session.get("typeahead"));
}
});
HTML
<template name="myTemplate">
<input data-source="{{typeahead}}" data-provide="typeahead" id="blah" type="text" />
</template>

Resources