I'm currently developing a ToDo List using Angular 8. When I enter in a task for my ToDo list it saves it in the Localstorage but when I refresh my page my tasks are gone but are still stored inside the localstorage of the browser.
How do I keep my tasks from disappearing after refreshing the browser, when they're still saved in LocalStorage?
import { Component, OnInit } from '#angular/core';
import { ToDo, IToDo } from './todo.model';
import { HttpClient } from '#angular/common/http';
import { LocalStorageService } from '../localStorageService';
import { ActivatedRoute, Router } from '#angular/router';
import { IUser } from '../login/login.component';
import { ToastService } from '../toast/toast.service';
#Component({
// tslint:disable-next-line: component-selector
selector: 'todolist',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class ToDoComponent implements OnInit {
todos: Array<IToDo> = [];
inputtask = "";
toDoParams = '';
localStorageService: LocalStorageService<IToDo>;
currentUser: IUser;
modal: any;
constructor(
private http: HttpClient,
private activatedRoute: ActivatedRoute,
private router: Router) {
this.localStorageService = new LocalStorageService('todos');
}
private toastService: ToastService;
async ngOnInit() {
const currentUser = this.localStorageService.getItemsFromLocalStorage('user');
console.log('from todos component', currentUser);
if (currentUser == null) {
this.router.navigate(['login']);
}
}
// Creating a to do item by clicking on the Enter Button
addToDo(todo: string) {
const td = {
id: 1,
task: todo,
editing: false
}
if (todo === '') {
alert('You must enter in a task TO DO!')
} else {
this.todos.push(td);
}
this.saveItemsToLocalStorage(this.todos);
}
delete(index: number) {
this.todos.splice(index, 1);
console.log("index", index);
this.saveItemsToLocalStorage(this.todos);
}
clear() {
this.todos = [];
console.log('index', this.todos)
this.saveItemsToLocalStorage(this.todos);
}
getItemsFromLocalStorage(key: string) {
const savedToDo = JSON.parse(localStorage.getItem(key));
console.log('from getItemsFromLocalStorage savedItems', savedToDo);
return this.localStorageService.getItemsFromLocalStorage(key);
return savedToDo;
}
saveItemsToLocalStorage(todos: Array<IToDo>) {
todos = this.sortByID(todos);
return this.localStorageService.saveItemsToLocalStorage(todos);
const savedToDo = localStorage.setItem('todos', JSON.stringify(todos));
console.log('from saveItemsToLocalStorage savedToDos: ', savedToDo);
return savedToDo;
}
sortByID(todos: Array<IToDo>) {
todos.sort((prevToDo: IToDo, presToDo: IToDo) => {
return prevToDo.id > presToDo.id ? 1 : -1;
});
console.log('the sorted ToDos', this.todos);
return this.todos;
}
logout() {
// clear localStorage
this.localStorageService.clearItemFromLocalStorage();
// navigate to login page
this.router.navigate(['']);
}
}
Here is the LocalStorageService file below
export class LocalStorageService<T> {
constructor(private key: string) {
}
saveItemsToLocalStorage(todos: Array<T> | T) {
const savedToDos = localStorage.setItem(this.key, JSON.stringify(todos));
console.log('from saveItemsToLocalStorage savedToDos: ', savedToDos);
return savedToDos;
}
getItemsFromLocalStorage(key?: string) {
let savedItems;
if (key != null) {
const items = null;
savedItems = JSON.parse(localStorage.getItem(key));
console.log('from getItemFromLocalStorage key: ', key, 'savedItems: ', savedItems);
} else {
savedItems = JSON.parse(localStorage.getItem(this.key));
}
return savedItems;
}
clearItemFromLocalStorage(key?: string) {
if (key != null) {
const items = null;
localStorage.setItem(key, JSON.stringify(items));
} else {
localStorage.clear();
}
}
}
Hey so when the page reloads angular loses context of the view so it has to render it again. You have all the working code here to make it work you just have to change the ngOnInit page load event so it reads them again and binds them to the property so angular can show them in the UI. Also bare in mind you call clearItemFromLocalStorage on logout so this won't grab them if they logout and then log back in, but i guess that was expected due to the code you have wrote.
The below should slot in and work for you:
async ngOnInit() {
const currentUser = this.localStorageService.getItemsFromLocalStorage('user');
console.log('from todos component', currentUser);
if (currentUser == null) {
await this.router.navigate(['login']);
} else {
// if user is logged in go and find any items from local storage and bind
// to the view
const toDoItems = this.localStorageService.getItemsFromLocalStorage('todos');
if (toDoItems && Array.isArray(toDoItems)) {
this.todos = toDoItems;
}
}
}
Related
I want to pass the array value from Search component to History component to display the history of the searches done.
I have written the code in this manner -
search-page.component.ts
export class SearchPageComponent implements OnInit {
constructor( private dataService :DataService) { }
githubSearch(username:any){
return new Promise((resolve, reject) => {
this.httpClient.get("----")
.pipe(map(Response => Response))
.subscribe((res: any) => {
this.searchResultObject = res;
this.allSearchResultArray.push(this.searchResultObject);
this.dataService.changeParam(this.allSearchResultArray)
resolve(this.searchResultObject );
});
});
}
passDataToService(){
this.dataService.allPassedData.next(this.allSearchResultArray);
}
}
data.service.ts
export class DataService {
allPassedData: any
constructor() { }
storePassedObject(passedData:any){
this.allPassedData.next(passedData);
}
retrievePassedObject(){
return this.allPassedData;
}
}
history-page.component.ts
export class HistoryPageComponent implements OnInit {
historyData : any = [];
constructor(private dataService: DataService) { }
ngOnInit(): void {
this.historyData = this.dataService.retrievePassedObject()
}
}
I am unable to retrieve data via this designed code.
First create subject in service and make it as observable
data.service.ts
export class DataService {
private allPassedData = new Subject<any>();
allPassedData$ = this.allPassedData.asObservable();
constructor() { }
setPassedData(retrievedData: any) {
this.allPassedData.next(retrievedData);
}
}
Now set the data in the observable
search-page.component.ts
passDataToService() {
this.dataService.setPassedData(this.allSearchResultArray);
}
history-page.component.ts
ngOnInit(): void {
// for retrieval of data in history component
this.dataService.allPassedData$.subscribe((data) => {
this.historyData = data
})
}
I have a service that does the following:
updateProperties(properties: any) {
return this.http.put(environment.adminApiURLPrefix+'api/v1/properties', properties);
}
In my component I have two objects, providers and providerProperties. Providers contains a key value pair of API providers and their status, whilst providerProperties is the actual response that is returned back from the API (has to be stored as any subsequent requests need the entire object).
import { Component, OnInit } from '#angular/core';
import { ProviderService } from '../../services/provider.service';
import { Status } from 'src/app/models/status.enum';
import { AlertService } from 'src/app/services/alert.service';
#Component({
selector: 'app-providers',
templateUrl: './providers.component.html',
styleUrls: ['./providers.component.scss']
})
export class ProvidersComponent implements OnInit {
providers: any;
providerProperties: any;
status: Status;
constructor(private providerService: ProviderService, private alertService: AlertService) {
this.providers = {
'API1': false,
'API2': false,
'API3': false
}
this.status = Status.Loading;
}
ngOnInit() {
this.providerService.getProperties().subscribe((response: any) => {
this.setProviderValues(response);
this.status = Status.Ready;
},
error => {
this.alertService.error('Error');
this.status = Status.Error;
})
}
setProviderValues(response: any) {
this.providerProperties = response;
Object.keys(this.providers).forEach(key => {
let providerStatus: string = this.providerProperties[key.toLowerCase() + '.enabled'];
if(providerStatus == 'true' || providerStatus == 'false') {
this.providers[key] = providerStatus == 'true' ? true : false;
} else {
this.alertService.error('Error.');
this.status = Status.Error;
return;
}
});
}
changeProviderProperty(provider: string, isEnabled: boolean) {
let providerKey: string = this.providerDisplayNameToPropertyKey(provider);
let tempProviderProperties = Object.assign({}, this.providerProperties);
tempProviderProperties[providerKey] = isEnabled ? true : false;
this.providerService.updateProperties(tempProviderProperties).subscribe(
response => {
this.setProviderValues(response);
this.alertService.success('Successfully '+ (isEnabled ? 'Enabled ' : 'Disabled ') + provider);
},
err => {
if(err['status'] == 0) {
this.alertService.error('Error');
} else {
let errorArray = err['error']['Errors']['Error'];
for(let errorKey in errorArray) {
let errorValue = errorArray[errorKey];
this.alertService.error('Error Updating Properties: ' + errorValue['ReasonCode'] + ' ' + errorValue['Description']);
}
}
}
)
}
providerDisplayNameToPropertyKey(provider: string) {
return provider.toLowerCase() + '.enabled';
}
}
I have the following test:
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '#angular/core/testing';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { ProvidersComponent } from './providers.component';
import { ProviderService } from 'src/app/services/provider.service';
import { of, Observable, throwError } from 'rxjs';
import { AlertComponent } from '../shared/alert/alert.component';
import { RouterTestingModule } from '#angular/router/testing';
import { AlertService } from 'src/app/services/alert.service';
import { HttpErrorResponse } from '#angular/common/http';
describe('ProvidersComponent', () => {
let component: ProvidersComponent;
let providerService: ProviderService;
let alertService: AlertService;
let fixture: ComponentFixture<ProvidersComponent>;
let getPropertiesSpy: jasmine.Spy<() => Observable<String>>;
let changeProviderPropertySpy: jasmine.Spy<(provider: string, isEnabled: boolean) => Promise<void>>;
let providerProperties:any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
ProvidersComponent,
AlertComponent
],
imports: [
HttpClientTestingModule,
RouterTestingModule
],
providers: [
ProviderService,
AlertService
]
})
.compileComponents();
}));
beforeEach(() => {
providerService = TestBed.get(ProviderService);
alertService = TestBed.get(AlertService);
fixture = TestBed.createComponent(ProvidersComponent);
providerProperties = {
'api1.enabled': 'true',
'api2.enabled': 'true',
'api3.enabled': 'false',
};
component = fixture.componentInstance;
component.providers = {
'API1': false,
'API2': false,
'API3': false
};
getPropertiesSpy = spyOn(providerService, 'getProperties').and.callFake(() => {
return of(providerProperties);
});
changeProviderPropertySpy = spyOn(component, 'changeProviderProperty');
fixture.detectChanges();
});
it('should enable provider', fakeAsync(() => {
spyOn(providerService, 'updateProperties').and.returnValue(of({
'api1.enabled': 'true',
'api2.enabled': 'false',
'api3.enabled': 'false'
}));
component.changeProviderProperty('API1', true);
fixture.detectChanges();
expect(component.providers['API1']).toEqual(true);
}));
});
For some reason the state of the objects won't change. I'm fairly sure the observable isn't being subscribed to or I am not waiting for the response.
Update
After spying on the provider service and checking to see if it had been called after I call changeProviderProperties, it turns out that it was never called.
Update 2
Spied on the providerDisplayNameToPropertyKey method and found that it's not being called for whatever reason.
The method component.changeProviderProperty contains asynchronous code. Because you're aware of that, you're running your test in the fakeAsync zone. What you're missing is a call to tick that simulates the asynchronous passage of time for the timers in the fakeAsync zone.
it('should enable provider', fakeAsync(() => {
...
component.changeProviderProperty('API1', true);
tick();
...
}));
Dear I am developing a page with Angular 7 and I am presented with the error TS2559: Type 'BookInterface[]' has no properties in common with type 'BookInterface', I have changed the code but I still can not find the solution, I leave the code below, the error is thrown in the method getListBooks(): this is my file list-books.component.ts
import { BookInterface } from './../../../models/book';
import { DataApiService } from './../../../services/data-api.service';
import { Component, OnInit } from '#angular/core';
import {NgForm} from '#angular/forms';
#Component({
selector: 'app-list-book',
templateUrl: './list-book.component.html',
styleUrls: ['./list-book.component.css']
})
export class ListBookComponent implements OnInit {
constructor(private dataApi: DataApiService) { }
private books: BookInterface = {};
ngOnInit() {
this.getListBooks();
}
getListBooks() {
this.dataApi.getAllBooks().subscribe(books => {
this.books = books;
});
}
onDelete() {
console.log('LIBRO ELIMINADO');
}
}
I also leave the code of my data-api.service.ts from where I call the interface
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs/internal/Observable';
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from '#angular/fire/firestore';
import { BookInterface } from '../models/book';
#Injectable({
providedIn: 'root'
})
export class DataApiService {
constructor(private afs: AngularFirestore) {
this.bookCollecction = afs.collection<BookInterface>('books');
this.books = this.bookCollecction.valueChanges();
}
private bookCollecction: AngularFirestoreCollection<BookInterface>;
private books: Observable<BookInterface[]>;
private bookDoc: AngularFirestoreDocument<BookInterface>;
private book: Observable<BookInterface>;
getAllBooks() {
return this.books = this.bookCollecction.snapshotChanges()
.pipe(map( changes => {
return changes.map( action => {
const data = action.payload.doc.data() as BookInterface;
data.id = action.payload.doc.id;
return data;
});
}));
}
// metodo que trae un libro a traves de su id
getOneBook(idBook: string) {
this.bookDoc = this.afs.doc<BookInterface>(`books/${idBook}`);
return this.book = this.bookDoc.snapshotChanges().pipe(map(action => {
if (action.payload.exists === false){
return null;
} else {
const data = action.payload.data() as BookInterface;
data.id = action.payload.id;
return data;
}
}));
}
addBook(book: BookInterface): void {
this.bookCollecction.add(book);
}
updateBook(book: BookInterface): void {
let idBook = book.id;
this.bookDoc = this.afs.doc<BookInterface>(`books/${idBook}`);
this.bookDoc.update(book);
}
deleteBook(idBook: string): void {
this.bookDoc = this.afs.doc<BookInterface>(`books/${idBook}`);
this.bookDoc.delete();
}
}
The version of typescript that I am currently using is Version 2.7.2, but also update it without solving the problem
You need to change the following:
private books: BookInterface = {};
to:
private books: BookInterface[] = [];
I know this question has been asked several times, but problem is that nobody tried to make a some fiddle or show results of code. This is what i have, i need to update values in other component based on value in some other component, but that is not just value,I have call function again in some other component.
I have some component that goes to database and update values, on second hand I have other component that read those values from database from service.
This is example of my code
tasks.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { environment } from '../../environments/environment';
import { Tasks } from './tasks';
#Injectable()
export class TasksProvider {
constructor(private http: HttpClient) { }
createNewTask(name: Name) : Observable<any> {
return this.http.post(environment.apiUri + 'tasks', { name, finished: false },
{ responseType: 'text' });
}
updateTask(id: Id, name: Name, finished: boolean) : Observable<any> {
return this.http.put(environment.apiUri + 'tasks/' + id, { name, finished },
{ responseType: 'text' });
}
getAllTasks(): Observable<Tasks[]> {
return this.http.get(environment.apiUri + 'tasks')
.map<any, Tasks[]>(data => data.map(Tasks.fromObject));
}
}
app.component.html
<app-tasks-list></app-tasks-list>
<app-tasks-add-new></app-tasks-add-new>
As you may see I have not child components, that is my main problem
tasks-list.component.ts
import {Component} from '#angular/core';
import { Tasks } from '../services/tasks';
import { TasksProvider } from '../services/tasks.service';
#Component({
selector: 'app-tasks-list',
templateUrl: './tasks-list.component.html',
styleUrls: ['./tasks-list.component.scss']
})
export class TasksListComponent {
tasks: Array<Tasks>;
constructor(private tasksProvider: TasksProvider) { }
ngOnInit() {
this.getTasksList();
}
displayedColumns: string[] = ['id', 'name', 'finished'];
private getTasksList() {
this.tasksProvider.getAllTasks()
.subscribe(tasks => {
this.tasks = tasks;
});
}
public updateCheckboxValue(id: number, name: string, event: any){
this.tasksProvider.updateTask(id, name, event.checked).subscribe(
result => {},
() => {
alert('Something went wrong');
})
}
}
tasks-add-new.component.ts
import { Component, OnInit, Inject } from '#angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '#angular/material';
import { Tasks } from '../services/tasks';
import { TasksProvider } from '../services/tasks.service';
export interface DialogData {
name: string;
}
#Component({
selector: 'app-tasks-add-new',
templateUrl: './tasks-add-new.component.html',
styleUrls: ['./tasks-add-new.component.scss']
})
export class TasksAddNewComponent implements OnInit {
ngOnInit() {
}
constructor(public dialog: MatDialog, private tasksProvider: TasksProvider) {}
openDialog(): void {
const dialogRef = this.dialog.open(TasksAddNewDialog, {
width: '250px',
data: {name: this.animal}
});
dialogRef.afterClosed().subscribe(result => {
this.name = result
this.tasksProvider.createNewTask(this.name).subscribe(
result => {},
() => {
alert('Something went wrong');
})
}
}
}
#Component({
selector: 'tasks-add-new-dialog',
templateUrl: 'tasks-add-new-dialog.html'
})
export class TasksAddNewDialog {
constructor(
public dialogRef: MatDialogRef<TasksAddNewDialog>,
#Inject(MAT_DIALOG_DATA) public data: DialogData) {}
onNoClick(): void {
this.dialogRef.close();
}
}
You see now when i call function in tasks-add-new.component.ts like
this.tasksProvider.createNewTask(this.name).subscribe(
result => {},
() => {
alert('Something went wrong');
})
I need to call again function in tasks-list.component.ts
private getTasksList() {
this.tasksProvider.getAllTasks()
.subscribe(tasks => {
this.tasks = tasks;
});
}
Does any body have idea how i can do that the best practice?
On of the possible approach is to use Subjects.
1) Store task list on the service and provide subscribable Subject
private tasks: Array<Task>;
public $tasks: BehaviorSubject<Array<Task>>;
constructor(private http: HttpClient) {
this.$tasks = new BehaviorSubject([]);
...
}
getAllTasks() {
this.http.get(environment.apiUri + 'tasks')
.subscribe(data => {
this.tasks = data;
this.$tasks.next(this.tasks);
});
}
updateTask(params) {
this.http.post(/* params */).subscribe((task) => {
this.tasks = this.tasks.map(t => t.id !== task.id ? t : task);
this.$tasks.next(this.tasks);
});
}
createTask(...) {
// again, do a request, update this.tasks and call $tasks.next
...
}
2) Make one service Subject subscription on the component instead of multiple service methods Observable listeners and update component's list automatically each time the service source has been changed
tasks: Array<Tasks>;
constructor(private tasksProvider: TasksProvider) {
this.tasksProvider.$tasks.subscribe(tasks => this.tasks = tasks);
}
ngOnInit() {
this.tasksProvider.getAllTasks();
}
public updateCheckboxValue(id: number, name: string, event: any){
this.tasksProvider.updateTask(id, name, event.checked);
}
I have a issue when I try to do the login. Step by step:
- The user clicks the login button;
- Auth0 appears to do the login;
- The user profile is saved in localStorage;
- When login is successful the internal page is loaded and the user can use the system. Every page need the profile data (in localStorage).
The problem
It's impossible to enter in the system in the first login. IT's EMPTY!!! even if the login was successful! I set a flow to logout the system when the localStorage is empty, so it's redirect to the login page.
BUT when you try again, everything goes fine! I have no idea why.
Follows the code.
AuthService
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { ApiService } from './api.service';
import { Profile } from '../models/Profile';
import auth0 from 'auth0-js';
import 'rxjs/add/operator/filter';
#Injectable()
export class AuthService {
auth0 = new auth0.WebAuth({
// Credentials
});
constructor(protected router: Router, protected api: ApiService) {}
public login(): void {
this.auth0.authorize();
}
public logout(): void {
localStorage.removeItem('profile');
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
localStorage.removeItem('access_token');
this.router.navigate(['/']);
}
private setSession(authResult): void {
const profile = authResult.idTokenPayload;
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
localStorage.setItem('expires_at', expiresAt);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('access_token', authResult.accessToken);
this.api.getUsuario(profile.name)
.subscribe(res => {
profile.nivel = res.nivel;
profile.idClube = res.idClube;
localStorage.setItem('profile', JSON.stringify(profile));
});
}
public handleAuthentication(): void {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
window.location.hash = '';
this.setSession(authResult);
this.router.navigate(['/calendario_']);
} else if (err) {
this.router.navigate(['/login']);
console.error(err);
}
});
}
public isAuthenticated(): boolean {
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return new Date().getTime() < expiresAt;
}
}
LoginComponent
import { Component } from '#angular/core';
import { AuthService } from '../../services/auth.service';
#Component({
moduleId: module.id,
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
constructor(protected auth: AuthService) { }
}
Component (After successful login)
import { Component, OnInit } from '#angular/core';
import { Profile } from '../../models/Profile';
import { Calendario } from '../../models/Calendario';
import { ApiService } from '../../services/api.service';
import { AuthService } from '../../services/auth.service';
#Component({
moduleId: module.id,
selector: 'app-calendarioproximo',
templateUrl: './calendarioProximo.component.html'
})
export class CalendarioProximoComponent implements OnInit {
protected title: string;
protected dataAtual: any;
protected loading = true;
protected profile: Profile;
protected model: Calendario[] = [];
protected calendario: Calendario[] = [];
constructor(protected api: ApiService, protected auth: AuthService) { }
getCalendario() {
this.api.getCalendario(this.profile.idClube)
.subscribe(res => {
this.loading = true;
this.model = res;
this.api.getData()
.subscribe(data => {
this.dataAtual = data.dataCompleta;
for (let cont = 0; cont < this.model.length && this.calendario.length < 5; cont++) {
if (this.model[cont].data >= this.dataAtual) {
this.calendario[this.calendario.length] = this.model[cont];
}
}
this.loading = false;
}, err => console.error(err));
});
}
ngOnInit() {
this.title = 'Calendário Próximo';
this.profile = new Profile();
// HERE!
JSON.parse(localStorage['profile']) ? this.profile = JSON.parse(localStorage['profile']) : this.auth.logout();
this.getCalendario();
window.scrollTo(0, 0);
}
}
Your setSession(authResult) function does not save the profile at the same time when it saves expires_at, id_token, and access_token.
The assignment will happen eventually, as a part of the handler in
getUsuario(profile.name).subscribe(() => {...})`.
Moving this.router.navigate(['/calendario_']); from handleAuthentication into setSession may resolve your issue:
private setSession(authResult): void {
const profile = authResult.idTokenPayload;
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
localStorage.setItem('expires_at', expiresAt);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('access_token', authResult.accessToken);
this.api.getUsuario(profile.name)
.subscribe(res => {
profile.nivel = res.nivel;
profile.idClube = res.idClube;
localStorage.setItem('profile', JSON.stringify(profile));
this.router.navigate(['/calendario_']); // Navigate after you `profile` has been set for sure
});
}
public handleAuthentication(): void {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
window.location.hash = '';
this.setSession(authResult);
// this.router.navigate(['/calendario_']); // This is too early...
} else if (err) {
this.router.navigate(['/login']);
console.error(err);
}
});
}