Angular component doesn't assign value from observable service getter, why? - javascript

I'm working on building a set of filters, so I'm just trying to make use of the salesChannels array content in my view, which only gets populated when clicking the button with the test() function. The log in ngOnInit outputs an empty array the first time, but works correctly after pressing the button.
The getOrganisationChannels returns an observable.
What causes this behavior and how do I handle it properly? I tried using an eventEmitter to try and trigger the populating but that doesn't work.
TYPESCRIPT
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels: Array<any> = [];
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.organizationService.getOrganizationChannels(this.organizationId).subscribe(
salesChannels => {
this.salesChannels = salesChannels;
})
}
test() {
console.log(this.salesChannels);
}
}
HTML
<div>
{{ salesChannels | json }}
</div>
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>

This is expected behaviour since you are populating the salesChannel in the subscription of an Observable. It's recommended that you use aysnc pipe to let angular check for changes and update the view accordingly.
Component.ts :
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels$!: Observable<Array<any>>;
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.salesChannels$ = this.this.organizationService.getOrganizationChannels(this.organizationId);
}
test() {
console.log(this.salesChannels);
}
}
In your template:
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels$ | async; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>
More details: https://angular.io/api/common/AsyncPipe

I recommend using AsyncPipe here:
<div>{{ salesChannels | async}}</div>
and in .ts:
salesChannels = this.organizationService.getOrganizationChannels(this.organizationId)

Related

How to use debounce on List of items in angular

How to use debounce time in an angular input search while searching through the list of items.Should i use RXJS and how to put it in my function filterReports? Can you please show me in code what i should do?
Code here:
protected reports: Array<ReportGroupModel> = [];
protected filteredReports: Array<ReportGroupModel> = [];
constructor(
protected route: ActivatedRoute,
protected httpService: HttpService
) {
super(route);
this.titleIcon = 'fa-bar-chart';
this.titleSufixKey = 'reports';
this.filterReports = debounce(this.filterReports, 500);
}
filterReports(filter: any) {
const searchText = filter.target.value?.toUpperCase();
if (searchText) {
let newFilteredReports: Array<ReportGroupModel> = [];
for (let reportGroup of this.reports) {
let foundItems = reportGroup.items.filter(x => x.title.toUpperCase().includes(searchText));
if (foundItems && foundItems.length > 0) {
newFilteredReports.push(new ReportGroupModel(reportGroup.header, foundItems));
}
}
this.filteredReports = newFilteredReports;
}
else
this.filteredReports = this.reports;
}
ngOnInit() {
super.ngOnInit();
this.filteredReports = this.reports;
}
and here is html
<div class="d-flex">
<input search="text" class="form-control mw-200p ml-auto" (keyup)="component.filterReports($event)" autofocus placeholder="{{ 'search' | translate }}"/>
</div>
<div class="d-flex flex-wrap pl-2">
<div *ngFor="let report of component?.filteredReports" class="pr-5 pt-2" style="width: 350px;">
<h3>{{report.header | translate}}</h3>
<ul class="list-unstyled pl-1">
<li *ngFor="let item of report.items">
<i class="fa {{item.icon}} mr-h"></i>
<a class="link" [routerLink]="item.path"> {{item.title}} </a>
<p *ngIf="item.description" class="text-muted">{{item.description}}</p>
</li>
</ul>
</div>
</div>
The easiest way to solve this issue is to use the reactive form module. however, if you want to stick with ngModel, you can do something like this.
searchChanged = new Subject();
protected reports: Array<ReportGroupModel> = [];
protected filteredReports: Array<ReportGroupModel> = [];
and update the subject every time the keyup event update
<div class="d-flex">
<input search="text" class="form-control mw-200p ml-auto" (keyup)="onKeyUp($event)" autofocus placeholder="{{ 'search' | translate }}"/>
</div>
//.ts
onKeyUp(value: string) {
this.searchChanged.next(value)
}
Now, you can use searchChanged subject to debounce the event update
constructor(
protected route: ActivatedRoute,
protected httpService: HttpService
) {
...
this.searchChanged.pipe(
debounceTime(300)
// You better add takeUntil or something to unsubscribe this observable when this component destroy
).subscribe(value => {
this.filterReports(value);
})
}

remove item shopping cart angular

I would simply like to delete an item on click, I made a code but I have error, I've been stuck on it for 2 days.
ERROR TypeError: this.addedBook.indexOf is not a function
I have already asked the question on the site we closed it for lack of information yet I am clear and precise
Thank you for your help
service
export class BookService {
url: string = 'http://henri-potier.xebia.fr/books';
public booktype: BookType[];
item: any = [];
constructor(private http: HttpClient) { }
getBookList(): Observable<BookType[]> {
return this.http.get<BookType[]>(this.url);
}
addToBook() {
this.item.push(this.booktype);
}
}
addToBook() here for add book but i dont know how to use it to display added books in my ts file
ts.file
export class PaymentComponent implements OnInit {
addedBook: any = [];
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.addedBook = this.bookService.getBookList();
}
delete() {
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
}
}
html
<div class="product" *ngFor="let book of addedBook | async">
<div class="product-image">
<img [src]="book.cover" alt="book">
</div>
<div class="product-details">
<div class="product-title">{{book.title}}</div>
</div>
<div class="product-price">{{book.price | currency: 'EUR'}}</div>
<div class="product-quantity">
<input type="number" value="1" min="1">
</div>
<div class="product-removal">
<button class="remove-product" (click)="delete()">
Supprimé
</button>
</div>
interface
export interface BookType {
title: string;
price: number;
cover: string;
synopsis: string;
}
I think this.bookService.getBookList() returns Observable so for you case it is not the best solution use async pipe. You should simply subscribe to your server response and than asign it to your variable. and after deleting item only rerender your ngFor.
JS
export class PaymentComponent implements OnInit {
addedBook: any[] = [];
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
// Observable
this.bookService.getBookList().subscribe(response =>{
this.addedBook = response;
});
// Promise
/*
this.bookService.getBookList().then(response=>{
this.addedBook = response;
})*/
}
delete(){
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
// rerender your array
this.addedBook = [...this.addedBook];
}
}
HTML
<div class="product" *ngFor="let book of addedBook">
<div class="product-image">
<img [src]="book.cover" alt="book">
</div>
<div class="product-details">
<div class="product-title">{{book.title}}</div>
</div>
<div class="product-price">{{book.price | currency: 'EUR'}}</div>
<div class="product-quantity">
<input type="number" value="1" min="1">
</div>
<div class="product-removal">
<button class="remove-product" (click)="delete()">
Supprimé
</button>
</div>
UPDATE
I built a special stackblitz so you can see it in action
here is the link;
you can't use javascript splice on Observable stream, it is not an Array.
to be able to remove an item from a stream you need to combine it (the stream) with another stream (in your case) the id of the item you want to remove.
so first create 2 streams
// the $ sign at the end of the variable name is just an indication that this variable is an observable stream
bookList$: Observable<any[]>; // holds bookList stream
deleteBook$ = new Subject<{ id: string }>(); // holds book id stream
now pass the results you get from your database (which is an observable stream) to bookList$ stream you just created like that
ngOnInit(): void {
this.bookList$ = this.bookService.getBookList().pipe(
delay(0)
);
}
change your html template to that.. and pipe the results from database like that
<div class="product" *ngFor="let book of (bookList$ | sync)">
...
// make sure you include your`remove-product` button inside `*ngFor` loop so you can pass the `book id` you want to remove to the `delete()` function.
<button class="remove-product" (click)="delete(book)">
Supprimé
</button>
</div>
now back to your ts file where we gonna remove the item from the STREAM by modifying the Array and return a new stream.
bookList$: Observable<any[]>; // holds bookList stream
deleteBook$ = new Subject<{ id: string }>(); // holds book id stream
ngOnInit(): void {
this.bookList$ = this.this.bookService.getBookList().pipe(
delay(0)
);
combineLatest([
this.bookList$,
this.deleteBook$
]).pipe(
take1(),
map(([bookList, deleteBook]) => {
if (deleteBook) {
var index = bookList.findIndex((book: any) => book.id === deleteBook.id);
if (index >= 0) {
bookList.splice(index, 1);
}
return bookList;
}
else {
return bookList.concat(deleteBook);
}
})
).subscribe();
}
now all is left to do is remove the item
delete(book: any) {
this.deleteBook$.next({ id: book.id }); pass the book you want to remove to the stream, `combineLatest` will take care of the rest
}
if you make an exit please don't forget me :)
good luck!
From your code, we can see that getBookList() return an Observable. As addedBook is not a array reference it will won't have array methods. That is the cause for your issue.
If you want to do some operations from the service data, subscribe to the observable and store the reference of the value to addedBook.
export class PaymentComponent implements OnInit {
...
ngOnInit(): void {
this.bookService.getBookList().subscribe(
res => { this.addedBook = res }
);
}
...
}
And you need to remove the async keyword from your html
Typescript is mainly used to identify these kind of issues in compile time. The reason it doesn't throw error on compile time is that you've specified addedBook as any. While declaring you declare it as array and onInit you change it to observable, which can be avoided if you've specified type[] ex: string[]
I would suggest something like this
Service file
export class BookService {
url: string = 'http://henri-potier.xebia.fr/books';
//add an observable here
private bookUpdated = new Subject<bookType>();
public booktype: BookType[] = [];//initializa empty array
item: any = [];
constructor(private http: HttpClient) { }
//Ive changet the get method like this
getBookList(){
this.http.get<bookType>(url).subscribe((response) =>{
this.bookType.push(response);//Here you add the server response into the array
//here you can console log to check eg: console.log(this.bookType);
//next you need to use the spread operator
this.bookUpdated.next([...this.bookType]);
});
}
bookUpdateListener() {
return this.bookUpdated.asObservable();//You can subscribe to this in you TS file
}
}
Now in your TS file you should subscribe to the update listener. This is typically done in NgOnInit
Something like this:
export class PaymentComponent implements OnInit {
addedBook: BookType;
product:any;
constructor(private bookService: BookService) { }
ngOnInit(): void {
this.bookService.bookUpdateListener().subscribe((response)=>{
this.addedBook = response;//this will happen every time the service class
//updates the book
});
//Here you can call the get book method
this.bookService.getBookList();
}
delete() {
this.addedBook.splice(this.addedBook.indexOf(this.product), 1);
}
}
Essentially what happens is you are subscribed to when books get changed or updated. Now you can simply use addedBook.title or whatever you want in your HTML.

How to pass formarray to other component

I want to pass formarray to child component to display formarray values there. Below is the code, what I've tried so far. I am not able to find the way to display the formarray values in the child component.
app.component.html
<div [formGroup]="userForm">
<div formArrayName="users">
<div *ngFor="let user of users.controls; let i = index">
<input type="text" placeholder="Enter a Room Name" [formControlName]="i">
</div>
</div>
</div>
<button (click)="addUser()">Add Room</button>
<title [users]="users"></title>
app.component.ts
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
public get users(): any {
return this.userForm.get('users') as FormArray;
}
ngOnInit() {
this.userForm = this.fb.group({
users: this.fb.array([this.fb.control('')])
});
}
addUser() {
this.users.push(this.fb.control(''));
}
title.component.html
<div *ngFor="let user of users.controls">{{ user.value }}</div>
title.component.ts
#Input() users;
ngOnChanges(changes) {
console.log(changes);
}
But above code not displaying the formarray values in the child component.
Example stackblitz is here
title is a keyword and its already defined tag with HTML. just use some different name for the component selector
selector: 'title1',
STACKBLITZ DEMO

Angular 5 - Uncheck all checkboxes function is not affecting view

I'm trying to include a reset button on a Reactive form in Angular 5. For all form fields, the reset is working perfectly except for the multiple checkboxes, which are dynamically created.
Actually the reset apparently happens for the checkboxes as well, but the result is not reflected in the view.
service.component.html
<form [formGroup]="f" (ngSubmit)="submit()">
<input type="hidden" id="$key" formControlName="$key">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" formControlName="name">
</div>
<br/>
<p>Professionals</p>
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>
<br/>
<button class="btn btn-success" type="submit" [disabled]="f?.invalid">Save</button>
<button class="btn btn-secondary" type="button" (click)="resetForm($event.target.checked)">Reset</button>
</form>
service.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormControl, FormGroup, Validators, FormArray } from '#angular/forms'
import { AngularFireAuth } from 'angularfire2/auth';
import { Router, ActivatedRoute } from '#angular/router';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable} from 'angularfire2/database';
import { Service } from './service';
export class ServiceComponent implements OnInit {
f: FormGroup;
userId: string;
$key: string;
value: any;
services: FirebaseListObservable<Service[]>;
service: FirebaseObjectObservable<Service>;
professionals: FirebaseListObservable<Service[]>;
profs: FirebaseListObservable<Service[]>;
constructor(private db: AngularFireDatabase,
private afAuth: AngularFireAuth,
private route: ActivatedRoute,
private router: Router,
private fb: FormBuilder) {
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.services = db.list(`services/${this.userId}`);
})
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.professionals = this.db.list(`professionals/${this.userId}`);
})
}
ngOnInit() {
// build the form
this.f = this.fb.group({
$key: new FormControl(null),
name: this.fb.control('', Validators.required),
prof: this.fb.array([], Validators.required)
})
}
onChange(name:string, isChecked: boolean) {
const profArr = <FormArray>this.f.controls.prof;
if(isChecked) {
profArr.push(new FormControl(name));
console.log(profArr.value);
} else {
let index = profArr.controls.findIndex(x => x.value == name)
profArr.removeAt(index);
console.log(profArr.value);
}
}
resetForm(){
let profArr = <FormArray>this.f.controls.prof;
this.f.controls.name.setValue('');
profArr.controls.map(x => x.patchValue(false));
this.f.controls.$key.setValue(null);
}
}
service.ts
export class Service {
$key: string;
name: string;
professionals: string[];
}
The result of the code above, displayed by line <pre> {{f.value | json}} </ pre> is:
When I fill out the form:
{
"$key": null,
"name": "Test service",
"prof": [
{
"name": "Ana Marques",
"id": "-LEZwqy3cI3ZoYykonWX"
},
{
"name": "Pedro Campos",
"id": "-LEZz8ksgp_kItb1u7RE"
}
]
}
When I click on Reset button:
{
"$key": null,
"name": "",
"prof": [
false,
false
]
}
But checkboxes are still selected:
What is missing?
I would stop using FormControls to handle what is basically state.
You have some code which loads the professionals property of the component. Just add to that data a checked property and change the type of professionals to Service[]:
this.db.list(`professionals/${this.userId}`)
.subscribe(professionals => {
professionals.forEach(p => p.checked = false);
this.professionals = professional;
});
Btw, you don't have a checked property on your Service type, so either you extend it or transform professionals in something else (i.e. a CheckableService).
The template becomes:
<div *ngFor="let p of professionals; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange(p, $event.target.checked)" [checked]="p.checked"/>{{ p.name }}</label>
</div>
And the onChange method becomes:
onChange(professional: Service, isChecked: boolean) {
professional.checked = isChecked;
this.profArr = this.professionals.filter(p => p.checked);
}
It seems a lot cleaner to me (you will need to adjust for the checked parameter not being in the Service type, but the code is simply adaptable). No messing with controls, only data cleanly flowing through your component.
You are not referencing your checkboxes. Give them a name using the index.
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input [formControlName]="i" class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>
This is my checkall checkbox
<input type="checkbox" value="a" (click)="checkAll" [checked]="checkAll">
This i a regular checkbox, i used this whithin an ngfor
<input type="checkbox" value="a" (click)="check(object)" name="checkbox" [checked]="true">
And the checkall checkboxes function
let elements = document.getElementsByTagName('input');
if (this.checkAll) {
this.checkAll = false;
for (let i = 0; i < elements.length; i++) {
if (elements[i].type == 'checkbox') {
elements[i].checked = false;
}
}
}
else....the other way

Angular 2 localstorage

I'm trying to work with localstorage in angular 2. I'm using angular cli.
app.component.ts
export class AppComponent implements OnInit {
currentItem: string;
newTodo: string;
todos: any;
constructor(){
this.currentItem = (localStorage.getItem('currentItem')!==null) ? JSON.parse(localStorage.getItem('currentItem')) : [ ];
localStorage.setItem('currentItem', JSON.stringify(this.currentItem));
this.newTodo = '';
this.todos = [];
}
addTodo() {
this.todos.push({
newTodo: this.newTodo,
done: false
});
this.newTodo = '';
localStorage.setItem('currentItem', JSON.stringify(this.todos));
}
ngOnInit(): void {}
}
app.component.html
<div>
<form (submit)="addTodo()">
<label>Name:</label>
<input [(ngModel)]="newTodo" class="textfield" name="newTodo">
<button type="submit">Add Todo</button>
</form>
</div>
<ul class="heroes">
<li *ngFor="let todo of todos; let i=index ">
<input type="checkbox" class="checkbox" [(ngModel)]="todo.done" />
<span [ngClass]="{'checked': todo.done}">{{ todo.newTodo }}</span>
<span (click)="deleteTodo(i)" class="delete-icon">x</span>
</li>
</ul>
<div>
<button (click)="deleteSelectedTodos()">Delete Selected</button>
</div>
It's a simple ToDo list, but it doesn't persist the data when I reload page.
In chrome inspect > Application > Local Storage I see the data. when I reload page, the data still appears, but it doesn't appears on view and when I add a new todo item, the Local Storage delete old items and update with a new todo.
Does anyone know how to fix it?
use your code like this
constructor(){
this.currentItem = (localStorage.getItem('currentItem')!==null) ? JSON.parse(localStorage.getItem('currentItem')) : [ ];
this.todos = this.currentItem;
}
addTodo() {
let local_items = localStorage.getItem('currentItem')
local_items.push({
newTodo: this.newTodo,
done: false
});
localStorage.setItem('currentItem', JSON.stringify(local_items));
this.newTodo = '';
}
Reason:
at the time of adding you set array in localStorage which has only latest object not old objects.
on refreshing page you are not assigning localStorage objects to todo variable
I modified a little the code provided for Pardeep Jain, and woked!
export class AppComponent implements OnInit {
currentItem: string;
newTodo: string;
todos: any;
constructor(){
this.currentItem = (localStorage.getItem('currentItem')!==null) ? JSON.parse(localStorage.getItem('currentItem')) : [ ];
this.todos = this.currentItem;
}
addTodo() {
this.todos.push({
newTodo: this.newTodo,
done: false
});
this.newTodo = '';
localStorage.setItem('currentItem', JSON.stringify(this.todos));
}
ngOnInit(): void {}
}

Categories

Resources