I have application which stores a list of cars.I wanted to add Search property into my project.I decided to use pipe and ngModel for my searchBox in order to do that.I have an array of those cars being listened by subscribe() whenever anything changes on it.In the pipe,I have const obj variable and it filters the array according to ngModel from component as I wish.But angular gives error and says ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined: [object Object],[object Object],[object Object]'. Current value: 'undefined: '. .I guess its undefined at first thats why its happening.But I added if block in order to check undefined or not, not to get this error .Here's my components and pipe below
Car-List Component.html
<form class="example" style="margin:auto;max-width:200px">
<input [(ngModel)]="nameBrand" type="text" placeholder="Search.." name="search2">
</form>
<div class="row">
<div class="col-xs-12">
<app-car-item
*ngFor="let carEl of cars | orderName:nameBrand ;let i = index"
[car]="carEl"
[index]="i"
>
</app-car-item>
</div>
</div>
OrderPipe.ts
#Pipe({
name: 'orderName',
})
export class OrderNamePipe implements PipeTransform{
constructor(private carService:CarService)
{
}
transform(value: any, arg1: any)
{
const obj = this.carService.getCars().filter(s => s.brand.includes(arg1));
if(obj)
{
this.carService.setCars(obj);
}
}
}
Car-list.component.ts
ngOnInit() {
this.subscription=this.carService.carsChanged
.subscribe(
(cars:Car[])=>
{
this.cars=cars;
}
);
this.cars = this.carService.getCars();
}
Car.Service.ts
export class CarService
{
carsChanged=new Subject<Car[]>();
private cars: Car[]=[
new Car(
'Dodge Viper SRT10',
'Coupe',
2017,
645,
600,
'Gasoline',
'Used',
'http://cdn-ds.com/stock/2010-Dodge-Viper-SRT10-ACR-Ltd-Avail-Akron-OH/seo/ECL2585-1B3AZ6JZ6AV100278/sz_88264/b22eddbbf045c389bd39e4ede1328f13.jpg',
970000
),
new Car(
'Chevrolet Camaro',
'Coupe',
2012,
432,
600,
'Gasoline',
'Used',
'https://carimages.com.au/MdzpzcZ7iNNuMu7RlH3Eg5t30CM=/fit-in/800x540/filters:stretch(FFFFFF)/vehicles/used/2018/CHEVROLET/CAMARO/2018-CHEVROLET-CAMARO-used-78-JWF447-1.jpg',
274000
),
new Car(
'Bentley Continental-GT',
'Coupe',
2018,
601,
489,
'Gasoline',
'New',
'https://cdn1.autoexpress.co.uk/sites/autoexpressuk/files/2017/11/4bentleycontinentalgt.jpg',
4150000
)
]
constructor()
{}
setCars(cars: Car[]) {
this.cars = cars;
this.carsChanged.next(this.cars.slice());
getCars()
{
return this.cars;
}
}
Car-Model.ts
export class Car{
public brand:string;
public type:string;
public year:number;
public bhp:number;
public torque:number;
public fuel:string;
public condition:string;
public imageUrl:string;
public price:number;
constructor(brand:string,type:string,year:number,bhp:number,torque:number,fuel:string,condition:string,imageUrl:string,price:number)
{
this.brand=brand;
this.type=type;
this.year=year;
this.bhp=bhp;
this.torque=torque;
this.fuel=fuel;
this.condition=condition;
this.imageUrl=imageUrl;
this.price=price;
}
}
I don't think its a good way to use the data from the service inside your pipe. Try this pipe instead,
import { Pipe, PipeTransform, Injectable } from '#angular/core';
#Pipe({
name: 'orderName'
})
#Injectable()
export class OrderNamePipe implements PipeTransform {
transform(items: any[], field: string, value: string): any[] {
if (!items) {
return [];
}
if (!field || !value) {
return items;
}
return items.filter(singleItem => singleItem[field].toLowerCase().includes(value.toLowerCase()));
}
}
This way, your pipe is reusable. Change your template to,
<app-car-item
*ngFor="let carEl of cars | orderName : 'brand' : nameBrand ;let i = index"
[car]="carEl"
[index]="i"
>
</app-car-item>
The 'brand' is specified since you need to filter based on that field.
Related
I have a class Projects
export class Projects {
project_id: number;
project_name: string;
category_id: number;
project_type: string;
start_date: Date;
completion_date: Date;
working_status: string;
project_info: string;
area: string;
address: string;
city: string;}
Its Service class is
#Injectable()
export class ProjectsService {
constructor(private http: HttpClient) {}
//http://localhost:9090/projectInfo
private apiUrl = 'http://localhost:9090/projectInfo';
public findAll() {
return this.http.get(this.apiUrl);
}
getProducts(): Observable<ProjectsModule[]> {
return this.http.get<ProjectsModule[]>(this.apiUrl);
}
Component is
import { Component, OnInit } from '#angular/core';
import { ProjectsService } from '../projects.service';
import{Projects} from '../projects';
import { plainToClass, Transform, Expose, Type, Exclude } from 'class-transformer';
#Component({
selector: 'app-project-list',
templateUrl: './project-list.component.html',
styleUrls: ['./project-list.component.css'],
providers: [ProjectsService]
})
export class ProjectListComponent implements OnInit {
private projects:Projects[]=[];
stringObject: any;
constructor(
private projectsService: ProjectsService) { }
vandana='rahul';
ngOnInit() {
this.getAllProjects();
}
getAllProjects() {
this.projectsService.getProducts().subscribe((data: Projects[])=> {
this.stringObject =JSON.stringify(data)
let newTodo = Object.assign(new Projects(), data);
this.projects= <Projects[]>this.stringObject;
console.log("data -"+ this.projects)
console.log("Array -"+ this.stringObject)
console.log("data -"+ this.projects[1].project_info)
},
err => {
console.log(err);
}
);
}
When i am trying to read newTodo.project_id (or any property of class Projects) it is undefined
but newtodo is returning jsondata
output is
Please help me in getting values newtodo.project_id, newtodo.project_name and so on
You're assigning a JSON string to this.projects.
The JSON string is [{"projectId": 1, ... }].
So:
this.projects[1] evaluates to { (i.e. the second character in the string)
"{".project_id evaluates to undefined
You should assign the data itself to this.projects:
this.projects = data;
And then keep in mind that arrays in JavaScript are zero-based. Since you only have one object in your array, you'd have to print the projectId as follows:
console.log(this.projects[0].projectId);
Also, the properties of your Projects class don't match your JSON at all. Furthermore, Projects should probably be named Project, and should be an interface instead of a class.
HI I need to change print in front screen of user.
Example if is number 900 I need to print in screen 9...
or if values of input form which I got from backend 10 I need to print 1... or whatever..
<input class="select" type="text" formControlName="preparationTime">
this.form = this.formBuilder.group({
preparationTime: ['']
});
Write custom validator or pipe. This is example of pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'reverse' })
export class ReversePipe implements PipeTransform {
transform(value) {
let res = value.slice().reverse();
return res;
}
}
value - is value what you want to change;
res - result after manipulating with value;
This is example of custom validator:
function AgeValidator(control: AbstractControl): { [key: string]: boolean } | null {
if (control.value > 18) {
return { 'age': true };
}
return null;
}
Don't forget to add his to form element:
this.form = this.formBuilder.group({
preparationTime: ['',[AgeValidator]]
});
I have the following requirements:
I got two FormControl objects for select-elements mainSelect and subSelect that are required.
subSelect changes depending on the value from mainSelect.
When mainSelect changes to a value in which the value from subSelect isn't included subSelect needs to become invalid so the FormGroup both of the FormControl's are part of becomes invalid, too.
But if the value from subSelect is included subSelect needs to hold his actual value.
(A concrete example is described after the StackBlitz link.)
My problem solving this requirement:
If the value of mainSelect changes and the value of subSelect isn't included subSelect takes the first value of the list instead of becoming null/invalid.
So the SOLUTION would be if the selected value of 'subSelect' becomes null and no value is selected in the browser.
What I tried so far:
I tried to create a component and implement the ControlValueAccessor interface. Seems like here lies my problem. I think I don't really understand how that works.
I watched the following video on YouTube and read articles (1, 2) related to ControlValueAccessor, but still couldn't solve my problem.
This is part of my code:
Also you can find it on StackBlitz
Example
If in the browser MainSelect has the value thirdMainSelect and SubSelect has the value fifthSubSelect and MainSelect changes his value to firstMainSelect SubSelect should have no selected value.
select.component.ts
export class SomeObject {
value: string;
parameters: {[parameterName: string]: string} = {};
}
#Component({
selector: "app-select",
templateUrl: "./select.component.html",
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectComponent,
multi: true
}]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
#ViewChild("select", {static: true}) select: ElementRef;
#Input() tableId: string;
#Input() filter: { [parameterName: string]: string};
returnedTable: SomeObject[];
onChange: (_: any) => void;
onTouched: () => void;
selected: string;
constructor(private tableService: TableService) { }
loadTable(): void {
this.tableService.getTable(this.tableId, this.filter)
.subscribe(table => {
this.returnedTable = table;
});
}
ngOnChanges(): void {
this.loadTable();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
writeValue(value: string): void {
this.selected = value;
}
}
select.component.html
<select class="form-control" #select (change)="onChange($event.target.value)">
<option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>
app.component.ts
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
form: FormGroup;
containerObject: ContainerObject;
selectedMainValue: string;
constructor(private tableService: TableService,
private formBuilder: FormBuilder) {
}
ngOnInit(): void {
this.tableService.getContainerObject()
.subscribe(containerObject => {
this.containerObject = containerObject;
this.selectedMainValue = containerObject.mainSelect;
this.initForm();
});
}
private initForm(): void {
this.form = this.formBuilder.group({
mainSelect: [this.containerObject.mainSelect, Validators.required],
subSelect: [this.containerObject.subSelect, Validators.required]
});
this.subscribeToMainSelectChanged();
this.subscribeToSubSelectChanged();
}
onSubmit(): void {
if (this.form.valid) {
this.containerObject.mainSelect = this.form.get("mainSelect").value;
this.containerObject.subSelect = this.form.get("subSelect").value;
this.tableService.saveContainerObject(this.containerObject);
}
}
private subscribeToMainSelectChanged() {
this.form.get("mainSelect").valueChanges
.subscribe(mainSelect => {
this.selectedMainValue = mainSelect;
console.log(this.form.status);
});
}
private subscribeToSubSelectChanged() {
this.form.get("subSelect").valueChanges
.subscribe(() => {
console.log(this.form.status);
});
}
}
app.component.html
<div>
<form id="wrapper" [formGroup]="form" (ngSubmit)="onSubmit()">
<div id="left" class="form-group row">
<label for="mainSelect" class="col-form-label col-sm-2">MainSelect</label>
<div class="col-sm-6">
<app-select
id="mainSelect"
formControlName="mainSelect"
[tableId]="'mainSelectTable'"
[filter]="{firstType: 'firstParameter'}"
></app-select>
</div>
</div>
<div id="right" class="form-group row">
<label for="subSelect" class="col-form-label col-sm-2">SubSelect</label>
<div class="col-sm-6">
<app-select
id="subSelect"
formControlName="subSelect"
[tableId]="'firstParameter'"
[filter]="{firstType: 'firstParameter', secondType: selectedMainValue}"></app-select>
</div>
</div>
<p></p>
<button id="button" type="submit">Submit</button>
</form>
</div>
I think that it's only use valuesChange. If you has two arrays data and subdata and a form like
form = new FormGroup({
prop1: new FormControl(),
prop2: new FormControl()
});
A simple
this.form.get("prop1").valueChanges.subscribe(res => {
this.dataService.getData(res).subscribe(data=>{
this.subdata=data;
if (!this.subdata.find(x=>x.value==this.form.get("prop2").value))
this.form.get("prop2").setValue(null);
}).unsubscribe()
});
must be enough, see stackblitz
After a while trying to solve this problem only using the SelectComponent and especially writeValue, the following code did the job:
I changed the select.component.ts as following:
export class SomeObject {
value: string;
parameters: {[parameterName: string]: string} = {};
}
#Component({
selector: "app-select",
templateUrl: "./select.component.html",
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectComponent,
multi: true
}]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
#ViewChild("select", {static: true}) select: ElementRef;
#Input() tableId: string;
#Input() filter: { [parameterName: string]: string};
returnedTable: SomeObject[];
onChange: (_: any) => void;
onTouched: () => void;
selected: string;
constructor(private tableService: TableService) { }
loadTable(): void {
this.tableService.getTable(this.tableId, this.filter)
.subscribe(table => {
this.returnedTable = table;
if (!!this.select && !!this.select.nativeElement.value) {
this.writeValue(this.select.nativeElement.value);
this.onChange(this.selected);
}
});
}
ngOnChanges(): void {
this.loadTable();
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
writeValue(value: string): void {
if (!!this.returnedTable && !this.returnedTable.some(item => item.value === value)) {
this.selected = null;
} else {
this.selected = value;
}
}
}
And the select.component.html like this:
<select class="form-control" #select (change)="onChange($event.target.value)">
<option hidden *ngIf="!selected" value=""></option>
<option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>
To deselect an option:— option.selected = false.
Angular library with function decompilation at its core for $scope and Nested Forms, an Angular feature that indicates that Angular team doesn't know how to effectively use HTML. HTML doesn't allow nested forms so why the hell would you try to shoehorn the language to do that? More trouble than it's worth. Of course, you can't expect much better from guys like Bradley Green, former Angular JS manager.
I want to pass the value from select list - ListComponentComponent to sibling component - DisplayComponentComponent and display the value in the template of DisplayComponentComponent. I want to use shared service for that. I created service and I am passing the value on change. However when I want to console.log this value in my display component I can't see anything. Here is my code.
Display component
export class DisplayComponentComponent implements OnInit {
val: any;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.myService.val.subscribe(result => {
this.val = result
});
}
}
List
export class ListComponentComponent implements OnInit {
list: any;
selected: string;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.list = [
{
text: 'test1',
value: 'test1'
},
{
text: 'test2',
value: 'test2'
},
{
text: 'test3',
value: 'test3'
}
]
this.selected = this.list[0].value;
this.myService.update(this.selected);
}
getSelected(val) {
this.selected = val;
this.myService.update(this.selected);
}
}
Service
#Injectable()
export class MyServiceService {
public source = new Subject<any>();
val = this.source.asObservable();
update(input: any) {
this.source.next(input);
}
constructor() { }
}
The value should be displayed here:
<p>
{{result}}
</p>
https://stackblitz.com/edit/angular-7lhn9j?file=src%2Fapp%2Fmy-service.service.ts
If you wand to show the values on application load you need to change the subject to BehaviorSubject
private _onChanged: BehaviorSubject<any> = new BehaviorSubject({});
public val= this._onChanged.asObservable();
update(input: any) {
this._onChanged.next(input);
}
constructor() { }
Demo
You have to bind to the right value in your display-component.component.html part:
<p>
{{val}} <!--not {{result}}-->
</p>
I found a small thing in your code. instead of bellow
<p>
{{result}}
</p>
you should use
<p>
{{val}}
</p>
The value is getting updated everything is right.
val: any;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.myService.val.subscribe(result => {
console.log(result);
this.val = result
});
}
in HTML you are using {{result}} there is no such variable use {{val}} instead, or change variable name
result: any;
constructor(private myService: MyServiceService) { }
ngOnInit() {
this.myService.val.subscribe(res => {
console.log(result);
this.result = res
});
}
I've been digging around, and found out that I can use the following to use *ngFor over an object:
<div *ngFor="#obj of objs | ObjNgFor">...</div>
where ObjNgFor pipe is:
#Pipe({ name: 'ObjNgFor', pure: false })
export class ObjNgFor implements PipeTransform {
transform(value: any, args: any[] = null): any {
return Object.keys(value).map(key => value[key]);
}
}
However, when I have an object like this:
{
"propertyA":{
"description":"this is the propertyA",
"default":"sth"
},
"propertyB":{
"description":"this is the propertyB",
"default":"sth"
}
}
I am not quite sure how I can extract 'propertyA' and 'propertyB', so that it is accessible from the *ngFor directive. Any ideas?
UPDATE
What I want to do, is to present the following HTML:
<div *ngFor="#obj of objs | ObjNgFor" class="parameters-container">
<div class="parameter-desc">
{{SOMETHING}}:{{obj.description}}
</div>
</div>
Where something would be equal to propertyA and propertyB (this is how the object is structured). So, this would lead to:
propertyA:this is the propertyA
propertyB:this is the propertyB
Or instead of creating a pipe and passing an object to *ngFor, just pass Object.keys(MyObject) to *ngFor. It returns the same as the pipe, but without the hassle.
On TypeScript file:
let list = Object.keys(MyObject); // good old javascript on the rescue
On template (html):
*ngFor="let item of list"
Update
In 6.1.0-beta.1 KeyValuePipe was introduced https://github.com/angular/angular/pull/24319
<div *ngFor="let item of {'b': 1, 'a': 1} | keyvalue">
{{ item.key }} - {{ item.value }}
</div>
Plunker Example
Previous version
You could try something like this
export class ObjNgFor implements PipeTransform {
transform(value: any, args: any[] = null): any {
return Object.keys(value).map(key => Object.assign({ key }, value[key]));
}
}
And then on your template
<div *ngFor="let obj of objs | ObjNgFor">
{{obj.key}} - {{obj.description}}
</div>
Plunker
Just return the keys from the pipe instead of the values and then use the keys to access the values:
(let instead of # in the beta.17)
#Pipe({ name: 'ObjNgFor', pure: false })
export class ObjNgFor implements PipeTransform {
transform(value: any, args: any[] = null): any {
return Object.keys(value)//.map(key => value[key]);
}
}
#Component({
selector: 'my-app',
pipes: [ObjNgFor],
template: `
<h1>Hello</h1>
<div *ngFor="let key of objs | ObjNgFor">{{key}}:{{objs[key].description}}</div> `,
})
export class AppComponent {
objs = {
"propertyA":{
"description":"this is the propertyA",
"default":"sth"
},
"propertyB":{
"description":"this is the propertyB",
"default":"sth"
}
};
}
Plunker example
See also Select based on enum in Angular2
keys.pipe.ts
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'keys' })
export class KeysPipe implements PipeTransform {
transform(obj: Object, args: any[] = null): any {
let array = [];
Object.keys(obj).forEach(key => {
array.push({
value: obj[key],
key: key
});
});
return array;
}
}
app.module.ts
import { KeysPipe } from './keys.pipe';
#NgModule({
declarations: [
...
KeysPipe
]
})
example.component.html
<elem *ngFor="let item of obj | keys" id="{{ item.key }}">
{{ item.value }}
</elem>
no use pipes this it example
*ngFor="let Value bof Values; let i = index"
{{i}}