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 {}
}
Related
I am exploring TS and Angular. I wanted to update my parent component array with and EventEmitter call. I did it so far but then noticed I don't need to cause my parent array is connected to my component variables (I guess so, that why I ask).
I generate my child component dynamically and initialize my child objects from the parent array. Is my template-task task$ Object so a reference to to the $task object from my parent array ?
Parent component:
<div *ngIf="task" class="elements">
<app-template-task (ping)="update($event)" [task$]="task" *ngFor="let task of $tasks"></app-template-task>
</div>
Child component HTML:
<div class="checkbox">
<img *ngIf="task$.done" (click)="changeStatus($event)" src="../../../assets/checked.png" alt="">
<img *ngIf="!task$.done" (click)="changeStatus($event)" src="../../../assets/unchecked.png" alt="">
</div>
<div class="text">
<input type="text" [(ngModel)]="task$.todoText" (blur)="changeText($event)" placeholder="Name des Todo punkts">
</div>
TS from parent:
public task: boolean;
public taskDone: boolean;
public $tasks: Todo[];
public $tasksdone: Todo[];
constructor() {
this.task = true;
this.taskDone = true;
this.$tasks = [
{
id: 0,
todoText: "Task 1",
done: false,
position: 1
},
{
id: 1,
todoText: "Task 2",
done: false,
position: 2
}
]
this.$tasksdone = []
}
TS from Child:
#Input() task$!: Todo; //! erlaubt es das theoretisch task auch null sein darf
#Output() ping: EventEmitter<any> = new EventEmitter<any>();
constructor() {
}
public changeStatus(event? : any): void{
this.task$.done = !this.task$.done
this.sendEvent("changed")
}
public changeText(event? : any): void{
console.log(this.task$.todoText)
this.sendEvent("textChanged")
}
private sendEvent(eventIdentifier: string): void{
const eventObject: Eventping = {
label: eventIdentifier,
object: this.task$
}
this.ping.emit(eventObject)
}
Yes, you're passing a reference up there: [task$]="task"
Having references in data bindings is good. Mutating objects in child components on the other hand is a bad thing to do.
So you should only use your data from an #Input, not change it.
The only place to change data is the parent component (where it's originally located).
I would recommend you making your child component a "dumb" one (means that it only shows data and tells parent what's going on, and the parent changes the data accordingly).
child.component.html :
<div class="checkbox">
<img *ngIf="task.done" (click)="changeStatus()" src="../../../assets/checked.png" alt="">
<img *ngIf="!task.done" (click)="changeStatus()" src="../../../assets/unchecked.png" alt="">
</div>
<div class="text">
<input type="text" [value]="task.todoText" (change)="changeText($event)" placeholder="Name des Todo punkts">
</div>
child.component.ts :
#Input() task: Todo;
// you can check docs on Partial here: https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype
#Output() ping: EventEmitter<any> = new EventEmitter<Partial<Todo>>();
constructor(){}
public changeStatus(): void {
this.ping.next({ done: !this.task$.done });
}
public changeText(newText: string): void {
this.ping.next({ todoText: newText });
}
Then your parent component should handle the updates and finally change your precious data :)
parent.component.html :
<div *ngIf="task" class="elements">
<app-template-task (ping)="update(idx, $event)" [task]="task" *ngFor="let task of $tasks; let idx = index"></app-template-task>
</div>
parent.component.ts :
public task = true;
public taskDone = true;
public tasks: Todo[] = [
{
id: 0,
todoText: "Task 1",
done: false,
position: 1
},
{
id: 1,
todoText: "Task 2",
done: false,
position: 2
}
];
public tasksDone: Todo[] = [];
constructor(){
}
update(taskIdx: number, changes: Partial<Todo>) {
// here we update reference by creating a new array with the .map method
// it's needed for the Angular Change Detection worked.
this.tasks = this.tasks.map((task, idx) => {
if (idx === taskIdx) return { ...task, ...changes };
return task;
});
// do anything else if needed
}
Basically that's it. I hope it'll help :)
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)
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.
I have an array of objects like this and when I click the Remove Favorite button I want to delete the certain element from local storage. I'm deleting from the page with the removeLocal() function but it only deletes from the page, not from local storage. I want to delete it both. I'm generating random number when assigning local storage key. Is there way to access this key and delete the item?
html:
<input type="text" [(ngModel)]="profile" (ngModelChange)="detectChange($event)" (keyup)="findProfile()"
placeholder="Enter the username..." class="input">
<div style="background-color: lightslategrey;">
<ng-template [ngIf]="profile !== '' && user">
<img [src]="user.avatar_url" alt="" class="userAvatar">
<p>Username: {{user.login}}</p>
<p>Location: {{user.location}}</p>
<p>E-mail: {{user.email}}</p>
<p>Blog Link: {{user.blog}}</p>
<p>Member Since: {{user.created_at}}</p>
<button [routerLink]="['', user.login.toLowerCase(), user.id ]" class="viewProfileButton" a>View
Profile</button><br>
<button (click)="localStorage()" class="viewProfileButton">Add to Favorite</button>
</ng-template>
</div>
<div *ngIf="closeDiv">
<div style="background-color: rgb(106, 106, 170);" *ngFor="let item of display">
<p>Username: {{item.login}}</p>
<p>Location: {{item.location}}</p>
<p>ID: {{item.id}}</p>
<button (click)="removeLocal(item.id)" class="viewProfileButton">Remove Favorite</button>
</div>
</div>
<button (click)="consoleLog()" class="viewProfileButton">Console Log</button>
<router-outlet></router-outlet>
ts:
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnInit {
user: any;
profile: any;
display: any;
local: any;
randomNumber: any;
randomString: any;
idString: any;
keys: any;
closeDiv: boolean = true;
constructor(private userData: HttpService) {}
ngOnInit() {
this.display = Object.values(localStorage).map((val: any) => JSON.parse(val));
console.log('ngOnInit Works', this.display);
}
findProfile() {
this.userData.updateProfile(this.profile);
this.userData.getUser().subscribe((result) => {
this.user = result;
});
}
localStorage(id: any) {
this.randomNumber = Math.floor(Math.random() * 10000);
this.randomString = this.randomNumber.toString();
localStorage.setItem(this.randomString, JSON.stringify(this.user));
this.display = Object.values(localStorage).map((val: any) => JSON.parse(val));
console.log(this.display);
}
removeLocal(id: any) {
for (let i = 0; i < this.display.length; i++) {
if (this.display[i].id === id) {
this.display.splice(i, 1);
}
}
}
detectChange(ev: any) {
ev.length > 0 ? (this.closeDiv = false) : (this.closeDiv = true);
}
}
Add the call localStorage.removeItem(key) to your removeLocal function. Granted, you need to store your random keys somewhere, otherwise you will have to integrate this solution to parse through them.
removeLocal(id: any, key: string) {
for (let i = 0; i < this.display.length; i++) {
if (this.display[i].id === id) {
this.display.splice(i, 1);
localStorage.removeItem(key); // here
}
}
}
EDIT: After a conversation in the comments, this solution can be simplified to remove a variable from the function header by storing a storageKey variable within display.
removeLocal(id: any) {
for (let i = 0; i < this.display.length; i++) {
if (this.display[i].id === id) {
localStorage.removeItem(this.display[i].storageKey);
this.display.splice(i, 1);
}
}
}
I have 3 components 'starter-left-side', 'starter-content', 'camera'. 1 service 'generalParameters'. In the generalParameters, I have 2 properties; 'contentHeader' & contentDescription which has default string values respectively.
Upon initialization of the starter-content, I get these values from the generalParameters and render it in starter-content.html. When I want to go to the camera.component, I just click on the link to the camera.component via the starter-left-side also, I have a method in the starter-left-side that sets the property value of the generalProperties as soon as the link is clicked so it can be used by the starter-content again.
I can successfully change the values in the generalProperties but the problem is, it is not rendered in the starter-component anymore. I do not know on which time of the life cycle hooks should I get the values from the generalProperties again so it can be rendered in the starter-content.html.
generaParameters.service.ts
contentHeader: string;
contentDescription: string;
constructor() {
this.contentHeader = "Dashboard";
this.contentDescription = "This is your dashboard";
}
starter-content.component.html
<h1>
{{pageHeader}}
<small>{{description}}</small>
</h1>
starter-content.component.ts
pageHeader: string;
description: string;
constructor(
private gp: GeneralparametersService
) { }
ngOnInit() {
this.pageHeader = this.gp.contentHeader;
this.description = this.gp.contentDescription;
}
starter-left-side.component.ts
setContent(header, description) {
this.gp.contentHeader = header;
this.gp.contentDescription = description;
}
starter-left-side.component.html
<li class="active"><i class="fa fa-link"></i> <span>Camera</span></li>
Thank you very much for your help.
Since you are communicating using a service you can propagate your changes using an Subject
When you make changes to your subject via the gp.setContent since your other component is observing the changes they will be automatically updated.
I used pluck so that we can only take the properties we need and render them separately.
See my implementation. Hope it helps!!!
starter-left-side.component.html
<li class="active"><i class="fa fa-link"></i> <span>Camera</span></li>
generaParameters.service.ts
import { Subject } from 'rxjs';
private mycontent$ = new Subject();
public content$ = this.mycontent$.asObservable();
setContent(header, description) {
this.content$.next({header, description});
}
starter-content.component.ts
import { pluck } from 'rxjs/operators';
ngOnInit(): void {
this.pageHeader$ = this.gp.content$.pipe(pluck('header'));
this.pageDescription$ = this.gp.content$.pipe(pluck('description'));
}
starter-content.component.html
<h1>
{{pageHeader$ | async }}
<small>{{pageDescription$ | async}}</small>
</h1>
Use a Subject or BehaviorSubject in your Service. Thus, all components get updated when the value changes:
generaParameters.service.ts
import {BehaviorSubject, Observable} from 'rxjs';
contentHeader: BehaviorSubject<string> = new BehaviorSubject('Dashboard');
contentDescription: BehaviorSubject<string> = new BehaviorSubject('This is your dashboard');
constructor() {}
public getContentHeader(): Observable<string> {
return this.contentHeader.asObservable();
}
public setContentHeader(value: string): void {
this.contentHeader.next(value);
}
public getContentDescription(): Observable<string> {
return this.contentDescription.asObservable();
}
public setContentDescription(value: string): void {
this.contentDescription.next(value);
}
starter-content.component.html
<h1>
{{pageHeader}}
<small>{{description}}</small>
</h1>
starter-content.component.ts
pageHeader: string;
description: string;
constructor(
private gp: GeneralparametersService
) { }
ngOnInit() {
this.gp.getContentHeader().subscribe(value => {
this.pageHeader = value;
});
this.gp.getContentDescription().subscribe(value => {
this.contentDescription = value;
});
}
starter-left-side.component.ts
ngOnInit() {
this.gp.getContentHeader().subscribe(value => {
this.pageHeader = value;
});
this.gp.getContentDescription().subscribe(value => {
this.contentDescription = value;
});
}
setContent(header, description) {
this.gp.setContentHeader(header);
this.gp.setContentDescription(description);
}
starter-left-side.component.html
<li class="active"><i class="fa fa-link"></i> <span>Camera</span></li>