What I want to achieve is that when I write inside input field "Foo"
it will become {{Foo}}
First create this directive :
#Directive({
selector: '[format-input]',
})
export class FormatDirective implements DoCheck {
valueIsNull:boolean = true;
constructor(public _elementRef: ElementRef<HTMLInputElement>,
private _renderer: Renderer2) { }
ngDoCheck(): void {
setTimeout(() => {
if(this.valueIsNull){
this.format();
}
}, 150)
fromEvent(this._elementRef.nativeElement, 'blur')
.pipe(
debounceTime(150),
distinctUntilChanged(),
tap(() => {
this.format();
})
)
.subscribe();
}
format(){
this._elementRef.nativeElement.value = "{{ " + this._elementRef.nativeElement.value + " }}"
this.valueIsNull = false;
}
}
Then Import it to your module : e.g app.module :
#NgModule({
declarations: [
FormatDirective
],
imports: [CommonModule],
exports: [
FormatDirective
]
})
export class AppModule { }
Then you can use it anywhere you want :
<input type="text" format-input />
You should use angular templating to achieve this Official Documentation for templating and interpolation and a code sample is given below. this will help you to achieve your usecase.
https://angular.io/guide/interpolation
https://angular.io/api/forms/NgModel
import {Component} from '#angular/core';
import {NgForm} from '#angular/forms';
#Component({
selector: 'example-app',
template: `
<form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
<input name="first" ngModel required #first="ngModel">
<input name="last" ngModel>
<button>Submit</button>
</form>
<p>First name value: {{ first.value }}</p>
<p>First name valid: {{ first.valid }}</p>
<p>Form value: {{ f.value | json }}</p>
<p>Form valid: {{ f.valid }}</p>
`,
})
export class SimpleFormComp {
onSubmit(f: NgForm) {
console.log(f.value); // { first: '', last: '' }
console.log(f.valid); // false
}
}
Thanks
Rigin Oommen
Related
I have a list of users that I want to filter by their full_name and address. And I'm having trouble filtering their names using angular filter because their names are stored in first_name and last_name seperately.
Here's what I've done so far:
TS
userSearch = {
full_name: "",
address: "",
};
HTML
<input [(ngModel)]="userSearch.full_name" placeholder="Name" />
<input [(ngModel)]="userSearch.address" placeholder="Address" />
<tr *ngFor="let user of usersJson| filter:userSearch">
<td>{{user.first_name+' '+user.last_name}}</td>
<td>{{user.address}}</td>
</tr>
you must create your custom pipe
filter.pipe.ts
import { Pipe, PipeTransform } from "#angular/core";
#Pipe({
name: "filterUsers"
})
export class FilterPipe implements PipeTransform {
transform(users: any, full_name: string, address: string): any {
if (full_name || address) {
return users.filter(user => {
const user_full_name = `${user.first_name} ${user.last_name}`;
// This will return true if user_full_name is equal full_name
// or if user.address is equal address
// **Note** if you want them both to be true then change || to &&
return (
user_full_name.toLocaleLowerCase() ===
full_name?.toLocaleLowerCase() ||
user.address.toLocaleLowerCase() === address?.toLocaleLowerCase()
);
});
}
return users;
}
}
Import filter Pipe in your module
import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
import { FilterPipe } from './filter.pipe';
#NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, HelloComponent, FilterPipe ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Use the pipe in your Components Html
<input [(ngModel)]="full_name" name="full_name" placeholder="Name"/>
<input [(ngModel)]="address" name="address" placeholder="Address"/>
<tr *ngFor="let user of users | filterUsers: full_name : address">
<td>{{user.first_name+' '+user.last_name}}</td>
<td>{{user.address}} </td>
</tr>
In your component ts file, you can add two variable
full_name: string;
address: string;
and also created a stackblitz link
I have an object like this.
types: {
2: {
zoom: true,
select: true
},
4: {
zoom: true,
select: true
},
}
Is it possible to create this json object from a angular form?
<div formGroupName="types">
<input type="number" matInput placeholder="Type" [(ngModel)]="searchType" i18n-placeholder>
<div [ngModelGroup]="searchType">
<mat-slide-toggle [(ngModel)]="searchType.zoom" color="primary">Zoom</mat-slide-toggle>
<mat-slide-toggle [(ngModel)]="searchType.select" color="primary">Klik</mat-slide-toggle>
</div>
</div>
Sadly it's not possible to change the json but the numbers need to be changable. The form is reactive driven but I could not find a way so I tried template driven but neither way worked out.
Using Angular reactiveForm and Factory to build your desire json object. It is possible to add property dynamically to your object. But your props name could not be some numbers as you mentioned in your example; If you want something its better to have an array of objects.
So, try something like this:
app.component.ts
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { TypeFactory } from './type.factory.ts';
import { TypeModel } from './app-type.model';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
typesForm:FormGroup;
types: TypeModel[] = [];
constructor(
private formBuilder: FormBuilder,
private typeFactory: TypeFactory,
){}
ngOnInit(){
this.typesForm = this.formBuilder.group({
id: ['', [Validators.required]],
zoom: [''],
select: ['']
});
}
regForm = () => {
let newType = this.typeFactory.createTypeDto(this.typesForm.getRawValues());
this.types = [ ...types, ...newType];
}
}
app.component.html
<form [formGroup]="typesForm">
<input formControlName="id" type="number" matInput placeholder="Type" i18n-placeholder>
<div class="container">
<mat-slide-toggle formControlName="zoom" color="primary">Zoom</mat-slide-toggle>
<mat-slide-toggle formControlName="select" color="primary">Klik</mat-slide-toggle>
</div>
<button class="btn btn-success" (click)="regForm()">Register</button>
</form>
app-type.model.ts
export class TypeModel {
id: number;
zoom: boolean;
select: boolean;
}
app.factory.ts
import { TypeModel } from './app-type.model';
export class TypeFactory {
createTypeDto(
form: any,
): TypeModel {
const model = new TypeModel();
model.id = form.id;
model.zoom = form.zoom;
model.select = form.select;
return model;
}
}
Below is my Component :
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { HttpService } from './http.service';
import { ProjectidService } from './projectid.service';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
projectDetailForm: FormGroup;
public submitted = false;
constructor(private fb: FormBuilder, private projectidvalidator: ProjectidService) { }
ngOnInit() {
this.projectDetailForm = this.fb.group({
projectid: ['', [Validators.required], [this.projectidvalidator.validate.bind(this.projectidvalidator)]],
projectname: ['name', Validators.required]
})
}
get f() { return this.projectDetailForm.controls; }
get validprojectid() { return this.projectDetailForm.get('projectid'); }
onSubmit(form: FormGroup) {
this.submitted = true;
// stop here if form is invalid
if (this.projectDetailForm.invalid) {
return;
}
console.log('Valid?', this.projectDetailForm.valid); // true or false
console.log('ID', this.projectDetailForm.value.projectid);
console.log('Name', this.projectDetailForm.value.projectname);
}
}
My Service :
import { Injectable } from '#angular/core';
import { Observable, of } from 'rxjs';
import { delay, tap, debounceTime } from 'rxjs/operators';
#Injectable()
export class HttpService {
constructor() { }
checkProjectID(id): Observable<any> {
// Here I will have valid HTTP service call to check the data
return of(true)
}
}
My Async validator :
import { HttpService } from './http.service';
import { Injectable } from '#angular/core';
import { AsyncValidator, AbstractControl, ValidationErrors } from '#angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, debounceTime, switchMap } from 'rxjs/operators';
#Injectable()
export class ProjectidService {
constructor(private _httpService:HttpService) { }
validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
console.log(control.value);
return control.valueChanges.pipe(
debounceTime(500),
switchMap(_ => this._httpService.checkProjectID(control.value).pipe(
map(isTaken => {
console.log(isTaken);
if (isTaken) {
return { noproject: true }
} else {
return null
}
})
)),
catchError(() => null)
);
}
}
and template :
<form [formGroup]="projectDetailForm" name="projectdetails" (ngSubmit)="onSubmit(projectDetailForm)">
<div class="form-group">
<label for="id">Project ID</label>
<input type="text" class="form-control" id="id" [ngClass]="{ 'is-invalid': f.projectid.invalid && (f.projectid.dirty || f.projectid.touched) }" placeholder="Project ID" name="projectid" formControlName='projectid'>
<button type="button">Validate</button>
<div *ngIf="f.projectid.invalid && (f.projectid.dirty || f.projectid.touched)" class="invalid-feedback">
<div *ngIf="f.projectid.errors.required">Project ID is required</div>
<div *ngIf="f.projectid.errors?.noproject">
Project id is not valid
</div>
</div>
<div *ngIf="f.projectid.errors?.noproject">
Project id is not valid
</div>
{{f.projectid.errors | json}}
</div>
<div class="form-group">
<label for="name">Project Name</label>
<input type="text" class="form-control" id="name" placeholder="Project Name" name="projectname" readonly formControlName='projectname'>
</div>
<div class="form-group d-flex justify-content-end">
<div class="">
<button type="button" class="btn btn-primary">Cancel</button>
<button type="submit" class="btn btn-primary ml-1">Next</button>
</div>
</div>
</form>
Problem is my custom async validation error message is not getting displayed.
Here is stackblitz example
You could do it as follows using rxjs/timer:
import { timer } from "rxjs";
....
return timer(500).pipe(
switchMap(() => {
if (!control.value) {
return of(null);
}
return this._httpService.checkProjectID(control.value).pipe(
map(isTaken => {
console.log(isTaken);
if (isTaken) {
return { noproject: true };
} else {
return null;
}
})
);
})
);
Sample
The real problem is and I have encountered this myself, you subscribe to the value change but you need to wait for the statuschange to return.
It is "PENDING" while it is doing the call.
The debounce/timer/... are just 'hacks' since you never know when the value is returned.
Declare a variable:
this.formValueAndStatusSubscription: Subscription;
In your
this.formValueAndStatusSubscription =
combineLatest([this.form.valueChanges, this.form.statusChanges]).subscribe(
() => this.formStatusBaseOnValueAndStatusChanges = this.form.status
);
Don't forget to desstroy the subscription
The most important point in the async validation is as descriped in Angular Doc
The observable returned must be finite, meaning it must complete at
some point. To convert an infinite observable into a finite one, pipe
the observable through a filtering operator such as first, last, take,
or takeUntil.
so basically you can use for example take(1) , it'll take the first emission then mark the Observable completed
return control.valueChanges.pipe(
debounceTime(500),
take(1),
switchMap(() =>
this._httpService.checkProjectID(control.value).pipe(
map(isTaken =>
isTaken ? { noproject: true } : null
)
))
)
demo
It's meant to multiply the 1 by 5, as you can see but I don't know how, I've tried stuff with ngModel but that didn't work.
This is the code in the HTML of the input and what has to be the total price:
<input type="number" style="width:40px; float:right;" />
<br>
<hr>
<p style="float: left;">Total price:</p>
<p style="float: right;"> <b>€{{activeProduct.price * inputNum}}</b>,-</p>
This is the model I made for the product:
This is the code in my TypeScript file:
import { Component, OnInit } from '#angular/core';
import { Product } from 'src/app/models/product';
#Component({
selector: 'app-tool-card',
templateUrl: './tool-card.component.html',
styleUrls: ['./tool-card.component.css']
})
export class ToolCardComponent implements OnInit {
public activeProduct: any;
public inputNum: number;
products: Product[] = [
new Product('Hammer', 'Hammer', 'Item used to hammer things', 'https://upload.wikimedia.org/wikipedia/commons/8/84/Claw-hammer.jpg', 1),
new Product('Saw', 'Hammer', 'I just saw a guy saying the n-word', 'https://media.screwfix.com/is/image//ae235?src=ae235/32045_P&$prodImageMedium$', 2),
new Product('Hit or miss', 'Hit or miss', 'I guess they never miss huh, mwah', 'https://pbs.twimg.com/media/Ds5mk0RU0AA1z_l.jpg', 5)
];
constructor() {
}
ngOnInit() {
}
public calculateTotal() {
this.activeProduct.price * this.inputNum;
}
public openModal(product): void {
// Copying object reference so we don't modify the original
this.activeProduct = Object.assign({}, product);
this.inputNum = 0;
}
}
You have not bound the input box to anything, use ngModel
<input type="number" [(ngModel)]="inputNum" style="width:40px; float:right;" />
or if you don't want to use the forms module
<input type="number" (change)="inputNum = $event.target.value" style="width:40px; float:right;" />
Make sure you add FormsModule to your imports array in your app.module.ts file
app.module.ts:
import { FormsModule } from '#angular/forms';
#NgModule({
declarations: [...],
imports: [
...
FormsModule
],
...etc
})
HTML File
<div>
<p>{{ activeProduct.price }} </p>
<label for="amount">Amount</label>
<input type="number" (change)="calculateTotal() [(ngModel)]="activeProduct.numOfItems"/>
<br>
<hr>
<p style="float: left;">Total price:</p>
<p [(ngModel)]="totalCost">{{ totalCost }}</p>
</div>
ts file
export class AppComponent {
activeProduct: any = {
price: 12,
name: 'pizza',
numOfItems: 0
};
totalCost: number = 0;
constructor() {}
calculateTotal(): number {
return this.totalCost = this.activeProduct.numOfItems * this.activeProduct.price;
}
}
not the most elegant way but it will get you there. I think the biggest gotcha is FormsModule
Hey Guys i wrote a little backend which returns some data. Now i want to fetch this data with Angular Http and show new values when i post them in the backend. So the first thing that came to my mind were Observables but currently i can fetch the data onInit but when Posting new Data to the Backend (currently just via Postman) the Fetched data wont update. So if this is the wrong approach tell me how to do this please. Below is my code i used so far:
App Component:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import {WeaponServiceService} from './weapon-service.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
weaponTypesarr: IweaponsTypes [] = [
{name: 'Nahkampf', value: 'melee'},
{name: 'Fernkampf', value: 'ranged'},
{name: 'Spezial', value: 'special'},
];
meleeTypesarr: IweaponsTypes [] = [
{name: 'Klingenwaffen', value: 'klinge'},
{name: 'Messer', value: 'messer'},
{name: 'Dolche', value: 'dolch'},
{name: 'Äxte/Beile', value: 'axt'},
{name: 'Speere/Stäbe', value: 'speer'},
{name: 'Stumpfe Hiebwaffen', value: 'stumpf'}
];
rangedTypesarr: IweaponsTypes [] = [
{name: 'Bogen', value: 'bogen'},
{name: 'Armbrust', value: 'armbrust'},
{name: 'Wurfwaffe', value: 'wurfwaffe'},
{name: 'kleine Schusswaffe', value: 'gun-litte'},
{name: 'große Schusswaffe', value: 'gun-big'}
];
specialTypesarr: IweaponsTypes [] = [
{name: 'Exotische Waffen', value: 'exotics'},
{name: 'Granaten und Exoplosive', value: 'grenade'}
];
rForm: FormGroup;
post: any;
weaponName = '';
weaponType= '';
impairment= '';
special= '';
results: Observable<any>;
constructor(private fb: FormBuilder , private weaponService: WeaponServiceService) {
this.rForm = fb.group({
'weaponName' : [null, Validators.required],
'weaponType': [null, Validators.required],
'impairment': [null, Validators.required],
'special': [null, Validators.required]
});
}
ngOnInit() {
this.results = this.weaponService.getWeapons();
this.results.subscribe(data => {console.log(data); });
}
generateWeapon(weaponData) {
this.weaponName = weaponData.weaponName;
this.weaponType = weaponData.weaponType;
this.impairment = weaponData.impairment;
this.special = weaponData.special;
console.log(weaponData);
}
}
export interface IweaponsTypes {
name: string;
value: string;
}
WeaponServiceService (didnt knew it calls it service by its own :D):
import { Injectable } from '#angular/core';
import {HttpClient} from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class WeaponServiceService {
constructor( private http: HttpClient) { }
getWeapons() {
return this.http.get('http://192.168.178.48:3000/getWeapons').map(data => {
return(data);
},
err => {
console.log(err);
}
);
}
createWeapon(weaponData2: any) {
return this.http.post('http://192.168.178.48:3000/createWeapon', weaponData2)
.map(
res => {
console.log(res);
},
err => {
console.log(err);
}
);
}
}
Module:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import {NgbModule} from '#ng-bootstrap/ng-bootstrap';
import { AppComponent } from './app.component';
import { HttpClientModule } from '#angular/common/http';
import {WeaponServiceService} from './weapon-service.service';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
ReactiveFormsModule,
NgbModule.forRoot()
],
providers: [WeaponServiceService],
bootstrap: [AppComponent]
})
export class AppModule { }
and last but not least the corresponding HTML but currently i just try to log all the values.
<div *ngIf="!name; else forminfo">
<form [formGroup]="rForm" (ngSubmit)="generateWeapon(rForm.value)">
<h1>Generate Weapon</h1>
<label for="WeaponName">WeaponName</label>
<input class="form-control" type="text" name="weaponName" formControlName="weaponName" id="WeaponName">
<div class="form-group">
<label for="WeaponGroup">Weapon Group</label>
<select class="form-control" #weaponTypeSelektor formControlName="weaponType" id="WeaponGroup">
<option> Select a Type</option>
<option *ngFor="let types of weaponTypesarr" [value]="types.value">{{types.name}}</option>
</select>
</div>
<div class="form-group" *ngIf="weaponTypeSelektor.value == 'melee'">
<label for="WeaponTypeMelee">Weapon Type</label>
<select class="form-control" formControlName="weaponType" id="WeaponTypeMelee">
<option *ngFor="let types of meleeTypesarr" [value]="types.value">{{types.name}}</option>
</select>
</div>
<div class="form-group" *ngIf="weaponTypeSelektor.value == 'ranged'">
<label for="WeaponTypeRanged">Weapon Type</label>
<select class="form-control" formControlName="weaponType" id="WeaponTypeRanged">
<option *ngFor="let types of rangedTypesarr" [value]="types.value">{{types.name}}</option>
</select>
</div>
<div class="form-group" *ngIf="weaponTypeSelektor.value == 'special'">
<label for="WeaponTypeSpecial">Weapon Type</label>
<select class="form-control" formControlName="weaponType" id="WeaponTypeSpecial">
<option *ngFor="let types of specialTypesarr" [value]="types.value">{{types.name}}</option>
</select>
</div>
<label for="impairment">Beeinträchtigung</label>
<input class="form-control" type="text" name="Beeinträchtigung" formControlName="impairment" value="" id="impairment">
<label for="special">Spezial</label>
<input class="form-control" type="text" name="Spezial" formControlName="special" value="" id="special">
<br><br>
<input type="submit" class="btn btn-primary" value="Submit Form" [disabled]="!rForm.valid">
</form>
<div *ngFor="let item of results | async"> {{item.weaponName}} </div>
</div>
<ng-template #forminfo>
<div class="form-container">
<div class="row columns">
<h1>{{ name }}</h1>
<p>{{ weaponType }}</p>
</div>
</div>
</ng-template>
So just to be clear. AppComponent starts and fetched initial data. I post Data into the Db with postman. App Component doesn't recognize new Value.
Change these lines in the Service.
getWeapons() {
return this.http.get('http://192.168.178.48:3000/getWeapons').map(data => {
return data.json();
}
And Change these lines in the AppComponent
ngOnInit() {
this.results = this.weaponService.getWeapons();
//delete this line ---> this.results.subscribe(data => {console.log(data); });
}
since you are using the async pipe you dont need to subscribe.
Hope this helps.
Tested on:
Angular CLI: 13.1.4
Node: 17.3.0
Package Manager: npm 8.3.0
I know I don't have the best answer but one which works (at least).
Maybe it helps someone to continue further ;-)
I fetched the data once in the component's ngOnInit as well as in a function which pulls the database data after a defined interval/time has passed.
data: any;
baseUrl = 'foo_URL';
fetchInterval: 3000; // 3 Seconds
constructor(private http: HttpClient) {
}
// First Fetch
ngOnInit(): void {
this.http.get(this.baseUrl)
.pipe(takeUntil(this.destroy$))
.subscribe((fooData) => {
this.data = fooData;
})
}
// Interval Fetch
setInterval: any = setInterval(() => {
this.http.get(this.baseUrl)
.pipe(takeUntil(this.destroy$))
.subscribe((fooData) => {
// Compare for view update
if (JSON.stringify(fooData) !== JSON.stringify(this.data)) {
console.log('New stuff -> update view')
this.data = fooData;
}
});
}, fetchInterval)
// Shield Memory Leakeage after Component is deleted
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
This is generally a bad approach as ..
.. data is pulled although there is no update the data.
.. data may be pushed towards the view without any need (fixed).
.. the interval is (mostly) barely adjustable to fit the need of both sides, Client & Server.
It is recommended to at least check changes in the data before updating the view with the same data.
Maybe even offer an API which only serves a checksum before pulling the whole dataset.
Last:
The more advanced solution would be a two-way-binding.
Once an API is called which changes the data, angular gets an update for the backend. For this kind of solution you might want to take a look at socket.io ... ;-)