Ionic: NgModel not working on Page Compoent? - ionic-framework

ngModel is not working in an ionic app.
FormsModule and CommonModule are imported in the page.module.ts.
enter image description here
usage is as follows
enter image description here
enter image description here

this is an example demonstrating to use ngModel in angular:
import {Component} from '#angular/core';
#Component({
selector: 'example-app',
template: `
<input [(ngModel)]="name" #ctrl="ngModel" required>
<p>Value: {{ name }}</p>
<p>Valid: {{ ctrl.valid }}</p>
<button (click)="setValue()">Set value</button>
`,
})
export class SimpleNgModelComp {
name: string = '';
setValue() {
this.name = 'Nancy';
}
}
When using the ngModel within tags, you'll also need to supply
a name attribute so that the control can be registered with the parent
form under that name

Related

Angular validation message for maxlength attribute

I'm having some trouble with displaying error messages for the maxlength attribute in Angular.
Problem
Since the maxlength attribute don't allow more characters than the specified amount, I'm having trouble displaying my error message. Is there any way to turn of the default behavior (allow the user to type more characters), in order to display my error message.
Code for textarea
<textarea maxlength="10"
[(ngModel)]="title.value"
#title="ngModel"></textarea>
Code for Angular validation
<div *ngIf="title.errors && (title.dirty || title.touched)"
class="alert alert-danger">
<div [hidden]="!title.errors.maxlength">
Only 10 characters allowed.
</div>
</div>
If you want me to provide any additional information, please let me know.
you can work with Reactive forms to validate properly your form,
here is a simple example how to use reactive forms :
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'title-form',
template: `
<form novalidate [formGroup]="myForm">
<label>
<span>Full title</span>
<input type="text" placeholder="title.." formControlName="title">
</label>
<div*ngIf="myForm.controls['title'].touched && myForm.get('title').hasError('required')">
Name is required
</div>
<div *ngIf="myForm.controls['title'].touched && myForm.controls['title'].hasError('maxlength')">
Maximum of 10 characters
</div>
</form>
`
})
export class TitleFormComponent implements OnInit {
myForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
title: ['', [Validators.required, Validators.maxLength(10)]],
});
}
}
hope it helps u :)
You can achieve it by setting the condition directly on the length of the input. A span tag with *ngIf can show/hide the error message:
HTML
<textarea class="form-control" id="title"
type="number" name="title" [(ngModel)]="titleModel"></textarea>
<span style="color:red" *ngIf="titleModel?.length > 10">
10 max
</span>
Class:
...
titleModel = 'I have more than 10 characters'
...
DEMO

Form in form. Can there be inheritance of form controls?

I have two components: ParentComponent and ChildComponent:
parent.component.ts
<form #form="ngForm" (ngSubmit)="onSubmit(form)" novalidate>
<input type="text" name="firstControl" [(ngModel)]="firstControl" />
<input type="text" name="secondControl" [(ngModel)]="secondControl" />
<child-component>
</form>
{{form.value | json}}
child.component.ts
<input type="text" name="thirdControl" [(ngModel)]="thirdControl" />
<input type="text" name="fourthControl" [(ngModel)]="fourthControl" />
Now, {{form.value | json}} returns { "firstControl": "", "secondControl": "" } and it's clear. My question is: Is there a way to form enherit form controls from child component? What is the correct way to get { "firstControl": "", "secondControl": "", "thirdControl": "", "fourthControl": "" } for ParentComponent? Is it possible?
Update:
Indeed there is an easier way:
import { FormsModule, ControlContainer, NgForm, NgModel } from '#angular/forms';
#Component({
...
viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]
})
export class ChildComponent {}
See also
Angular2 nested template driven form
Previous version:
I would say it's possible. For example you could add the following code to your
child.component.ts
import { NgForm, NgModel } from '#angular/forms';
#Component({
selector: 'child-component',
template: `
<input type="text" name="thirdControl" [(ngModel)]="thirdControl" />
<input type="text" name="fourthControl" [(ngModel)]="fourthControl" />
`
})
export class ChildComponent {
#ViewChildren(NgModel) ngModels: QueryList<NgModel>;
constructor(#Optional() private ngForm: NgForm) {}
ngAfterViewInit() {
if (this.ngForm) {
this.ngModels.forEach(model => this.ngForm.addControl(model));
}
}
}
Plunker Example
Angular DI system gives us the opportunity to get reference to parent NgForm instance because angular dependency resolution algorithm starts with current node and goes up through tree of elements. In my example we can imagine the follwing tree
#NgModule providers
|
my-app
|
form
/ | \
input[text] input[text] child-component
So when we are requiring NgForm token angular will search it in the next order
child-component
||
\/
form
||
\/
my-app
||
\/
#NgModule
On form element NgForm directive is placed so when can get it. Also we can get any token that was declared on NgForm directive within providers array. And this rule applies to any node.
See also Angular 2 - How does ng-bootstrap provide the NgbRadioGroup and NgbButtonLabel to their NgbRadio directive?
Then i just added child NgModel directives manually to NgForm so they should work together.
Angular has two kinds of forms, Template Driven and Reactive. What you want to do isn't possible with Template Driven forms, you'll need to look into Reactive Forms
In general it works by creating a form object in the component, and using it in the template. Angular says this is actually more flexible and easier to test, but I think the syntax is a lot harder to wrap your head around.
You'll want this in your parent component:
myForm: FormGroup;
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.createForm();
}
createForm() {
this.myForm = this.fb.group({
firstControl: '',
secondControl: '',
thirdControl: '',
fourthControl: ''
});
}
Your parent template would look something like this:
<form [formGroup]="myForm" novalidate>
<input type="text"formControlName="firstControl" />
<input type="text" formControlName="secondControl" />
<child-component [parentFormGroup]="myForm">
</form>
And your child will accept the form group as an input, and the template will look something like this:
<div class="form-group" [formGroup]="parentFormGroup">
<input type="text" formControlName="thirdControl" />
<input type="text" formControlName="fourthControl" />
</div>

How can I create my own component for FormControls?

I would like to create a form and use a new, custom component for its controls. So I created a new component and included it into the parent form. But although the parent form has a formGroup, Angular complains that it does not.
The error:
Error: formControlName must be used with a parent formGroup directive.
You'll want to add a formGroup
directive and pass it an existing FormGroup instance (you can create one in your class).
The parent form has:
<form [formGroup]="learningObjectForm" (ngSubmit)="onSubmit()" novalidate>
<div>
<button type="submit"
[disabled]="learningObjectForm.pristine">Save</button>
</div>
<ava-form-control [label]="'Resource'"></ava-form-control>
</form>
And in the .ts:
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.learningObjectForm = this.fb.group({
text: '',
});
}
In the custom component I have
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'ava-form-control',
template: ` <div>
<label>{{label}} :</label>
<input formControlName="{{name}}">
</div>
`
})
export class FormControlComponent implements OnInit {
#Input() label: string;
#Input() name: string;
constructor() {}
ngOnInit() {
if (this.name === undefined) {
// turns 'The Label' into 'theLabel'
this.name = this.label[0].toLowerCase().concat(this.label.slice(1));
this.name = this.name.split(' ').join('');
console.log(this.label, this.name);
}
}
}
You should also be passing the formGroup instance along with control name to your custom component. And then create a form control under that formGroup in custom component. Your custom component will create the control virtually under the same formGroup that you have provided.
<form [formGroup]="learningObjectForm" (ngSubmit)="onSubmit()" novalidate>
<div>
<button type="submit"
[disabled]="learningObjectForm.pristine">Save</button>
</div>
<ava-form-control [label]="'Resource'" [formGroup]="learningObjectForm" [controlName]="'mycontrol'"></ava-form-control>
</form>
custom.component.ts
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'ava-form-control',
template: ` <div>
<label>{{label}} :</label>
<input [formControl]="formGroup.controls[controlName]>
</div>
`
})
export class FormControlComponent implements OnInit {
#Input() label: string;
#Input() formGroup: FormGroup;
#Input() controlName: string;
constructor() {}
ngOnInit() {
let control: FormControl = new FormControl('', Validators.required);
this.formGroup.addControl(this.controlName, control);
}
}
With this your parent component can access all the form controls defined within their respective custom components.
I played around with the accepted answer for a long time, and never had any luck.
I had much better results implementing the ControlValueAccessor interface as shown here:
https://alligator.io/angular/custom-form-control/
It's actually pretty simple, I also rigged up an Example StackBlitz

Angular 2 form, get method istead of post

While working on Angular 2 form submit I run into a problem. When I create an object inside a component everything works well and my form gets submit via post method. But when I am using an object from a class outside the component my form sends a get request with url http://localhost:4200/blog?title=sss&content=ssssss
Does anyone know why this is happening?
Template:
<form (ngSubmit)="onSubmit()" #f="ngForm">
<!-- <form #f="ngForm" (ngSubmit)="onSubmit(f)">-->
<div class="form-group">
<label for="title">Tytuł</label>
<textarea class="form-control" id="title" rows="1"
ngModel name = "title" required minlength="3" #title="ngModel"></textarea>
<span class="help-block" *ngIf="!title.valid && title.touched">Wprowadzono za krótki tekst (minum to 3 znaki).</span>
<label for="content">Zawartość:</label>
<textarea class="form-control" id="content" rows="3"
ngModel name = "content" required minlength="3" #content="ngModel"></textarea>
<span class="help-block" *ngIf="!content.valid && content.touched">Wprowadzono za krótki tekst (minum to 3 znaki).</span>
</div>
<button type="submit" class="btn btn-primary"
[disabled] ="!f.valid"
>Wyślij</button>
</form>
Component:
import {Component, OnInit, ViewChild} from '#angular/core';
import {NgForm} from "#angular/forms";
import {Blog} from "../../shared/blog";
import {BlogService} from "../../services/blog.service";
#Component({
selector: 'app-blog-form',
templateUrl: './blog-form.component.html',
styleUrls: ['./blog-form.component.css']
})
export class BlogFormComponent implements OnInit {
#ViewChild('f') form: NgForm;
errorMessage: string;
/* this works well
blog = {
title: '',
content: '',
dateCreated: ''
}*/
//this doesn't work
blog: Blog;
ngOnInit(){}
onSubmit(){
this.blog.title = this.form.value.title;
this.blog.content = this.form.value.content;
}
}
The Blog class. I tried both this:
export class Blog {
constructor(public title = '', public content = '', public dateCreated = ''){}}
And this:
export class Blog {
constructor(public title : string, public content : string, public dateCreated : string){}}
Thanks for any help :)
I am not sure why this is happening but try not using this.form.value.
onSubmit(){
this.blog.title = this.form.title;
this.blog.content = this.form.content;
console.log(this.blog);
}
Use of value posts back your page. Now this code should work.

Referencing individual generated components from ngFor

I have an angular2 component which I have included below. I generate a list of chapters which I then display with an *ngFor= tag, but I want to be able to individually target these in my ng2 component (so I can highlight the selected chapter). I would of thought the below code would generate something like this:
<p class="chapter 1" #1>1. My First Chapter</p>
However, I don't get the #1, hence my selector doesn't work and I can't by default set the first chapter in the list to be selected.
import { Component, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
#Component({
selector: 'tutorial',
template: `
<div class="content row">
<div class="chapters col s3">
<h3>Chapters:</h3>
<p *ngFor="let chapter of chapters" class="chapter" #{{chapter.number}}>{{chapter.number}}. {{chapter.title}}</p>
</div>
</div>
`
})
export class bookComponent implements AfterViewInit {
public chapters = _chapters;
#ViewChild('2') el:ElementRef;
ngAfterViewInit() {
this.el.nativeElement.className += " clicked";
}
}
What should I do to be able to individually select my generated <p> tags?
For you use case this might be a more angulary way
<p *ngFor="let chapter of chapters; let i=index" (click)="clickedItem = i" [class.clicked]="i == clickedItem" class="chapter" #{{chapter.number}}>{{chapter.number}}. {{chapter.title}}</p>
export class bookComponent implements AfterViewInit {
public chapters = _chapters;
clickedItem: number;
}
Updating the model and binding the view to make Angular reflect the model to the view is the preferred way instead of imperatively modifying the DOM.
I would let the NgFor loop control adding or removing the clicked class:
<p *ngFor="let chapter of chapters" class="chapter"
[class.clicked]="chapter.number === selectedChapterNumber">
{{chapter.number}}. {{chapter.title}}
</p>
Then just set selectedChapterNumber appropriately in your component logic.
export class bookComponent {
public chapters = _chapters;
private selectedChapterNumber = 1;
}
You can use directive with HostListener to select an element as shown below.
Working Demo : http://plnkr.co/edit/mtmCKg7kPgZoerqT0UIO?p=preview
import {Directive, Attribute,ElementRef,Renderer,Input,HostListener} from '#angular/core';
#Directive({
selector: '[myAttr]'
})
export class myDir {
constructor(private el:ElementRef,private rd: Renderer){
console.log('fired');
console.log(el.nativeElement);
}
#HostListener('click', ['$event.target'])
onClick(btn) {
if(this.el.nativeElement.className=="selected"){
this.el.nativeElement.className ="";
}else{
this.el.nativeElement.className ="selected";
}
}
}
//our root app component
import {Component} from '#angular/core';
#Component({
selector: 'my-app',
directives:[myDir],
template:
`
<style>
.selected{
color:red;
background:yellow;
}
</style>
<div class="content row">
<div class="chapters col s3">
<h3>Chapters:</h3>
<p myAttr *ngFor="let chapter of chapters" class="chapter" #{{chapter.number}}>{{chapter.number}}. {{chapter.title}}</p>
</div>
</div>
`
})
export class App {
chapters=[{number:1,title:"chapter1"},{number:2,title:"chapter2"},{number:3,title:"chapter3"}]
}