Messages disappearing after refreshing the page - javascript

I have a view with a list of messages. I would like them not to disappear after I refresh the browser page. I used this tutorial and created a local storage service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class LocalStorageService {
localStorage: Storage;
constructor() {
this.localStorage = window.localStorage;
}
get(key: string): any {
if (this.isLocalStorageSupported) {
return JSON.parse(this.localStorage.getItem(key)!);
}
return null;
}
set(key: string, value: any): boolean {
if (this.isLocalStorageSupported) {
this.localStorage.setItem(key, JSON.stringify(value));
return true;
}
return false;
}
remove(key: string): boolean {
if (this.isLocalStorageSupported) {
this.localStorage.removeItem(key);
return true;
}
return false;
}
get isLocalStorageSupported(): boolean {
return !!this.localStorage
}
}
#Component({
selector: 'app-message-receiver',
templateUrl: './message-receiver.component.html',
styleUrls: ['./message-receiver.component.scss']
})
export class MessageReceiverComponent implements OnInit {
pluginId: string = '';
messages$: Observable<Message[] | undefined>;
gatewayMessagesState$: Observable<GatewaysMessagesRegistry>;
sub?: Subscription;
constructor(private route: ActivatedRoute,
private store: Store<fromRoot.State>,
private location: Location,
private localStorageService: LocalStorageService
) {
this.gatewayMessagesState$ = store.select(fromRoot.getGatewayMessages);
this.sub = this.route.paramMap.subscribe(params => {
this.pluginId = params.get('gatewayId') || '';
})
this.messages$ = this.gatewayMessagesState$.pipe(
map(state => state.gatewaysMessagesMap.get(this.pluginId)?.list)
);
}
ngOnInit(): void {
}
ngOnDestroy() {
this.sub?.unsubscribe();
}
persist(key: string, value: any) {
this.localStorageService.set(key, value);
}
getCurrentPath() {
return this.location.path();
}
beautify(message: string) {
return vkbeautify.xml(message);
}
}
<div class="templates">
<a mat-list-item *ngFor="let message of (messages$ | async)?.reverse()">
<mat-expansion-panel class="panel">
<mat-expansion-panel-header>
<mat-panel-title>{{message.date | date:'mediumTime'}}</mat-panel-title>
<mat-panel-description class="description">
<div>{{message.title}}</div>
</mat-panel-description>
</mat-expansion-panel-header>
<div>
<pre>{{beautify(message.content)}}</pre>
</div>
</mat-expansion-panel>
<br>
</a>
</div>
The problem is, I have only basic knowledge of Angular and don't really know how to proceed. To be specific, I don't know how to incorporate the persist method into the HTML file. I'm also not sure if something more is needed.
I've tried using local storage without a separate service as well but didn't manage to succeed either.

Related

Refreshing the page after changing the URL

It seems to be a basic thing but I'm struggling. I have a view "/plugin/{pluginId}" that doesn't update when I hit enter after changing the pluginId in the address bar. I have to manually refresh the page in the browser. What can I do so that it refreshes automatically?
<div>
<div>
<a mat-raised-button href="/plugin">
<span>←</span>
</a>
<a mat-raised-button href="" style="float: right">Send message</a>
</div>
<div>
<ul>
<li *ngFor="let message of (messages$ | async)">
{{message.message}}
<br><br>
</li></ul>
</div>
</div>
export class Foo implements OnInit {
pluginMessagesState$: Observable< fromPluginMessages.State >;
pluginId = '';
messages$: Observable< Message[] >;
constructor(private route: ActivatedRoute,
private store: Store< fromRoot.State >,
) {
if ( this.route.snapshot.params[ 'pluginId' ] ) {
this.pluginId = this.route.snapshot.params[ 'pluginId' ];
}
this.pluginMessagesState$ = this.store.select( fromRoot.getPluginMessagesState );
this.messages$ = this.pluginMessagesState$.pipe(
map( state => state.pluginMessages.get( this.pluginId )?.list || [] )
);
}
ngOnInit(): void {
}
}
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-user-detail',
templateUrl: 'user-detail.component.html'
})
export class UserDetailComponent implements OnInit {
constructor(private activeRoute: ActivatedRoute) {
}
ngOnInit() {
const queryParams = this.activeRoute.snapshot.queryParams
const routeParams = this.activeRoute.snapshot.params;
// do something with the parameters
this.loadUserDetail(routeParams.id);
}
}
or
ngOnInit() {
this.activeRoute.queryParams.subscribe(queryParams => {
// do something with the query params
});
this.activeRoute.params.subscribe(routeParams => {
this.loadUserDetail(routeParams.id);
});
}
Does your router file look like this ?
{path: 'plugin/:id', component: UserDetailComponent}
If so, subscribe to ActivatedRoute (inject it in the constructor parameters) like this:
public pluginId: number;
constructor(private route: ActivatedRoute) {
}
And access the parameter by subscribing to route.params:
ngOnInit() {
this.routeSub = this.route.params.subscribe(params => {
// update the pluginId accordingly
this.pluginId = params['id'];
});
}

Angular how to call Method from Sibling Component that is binded via <router-outlet>?

I have a Project that uses HTTP Calls to fetch Data from API Endpoint and than display on Screen.
It's a simple ToDoList. So you can add Items to the list, see all Items in your List, delete items and so on.
The Project structure is this:
Items-Component (Holds the entire App basically)
Item-list-component
Item-detail-component
Item-edit-component
item-add-component
Item.service
The Items.component.html looks like this:
<div class="row">
<div class="col-md-5">
<app-item-list></app-item-list>
</div>
<div class="col-md-7">
<router-outlet></router-outlet>
</div>
So we can see that the item-list-component and the other 3 components (binded via router-outlet) are sibling components, that's what I think.
So my Problem is now:
I want that whenever a new Item is created the items[] in the items.list component should refresh automatically. Now I must click a "Fetch Items" button to refresh the items[].
When I add a new Item, it fires a method from my item.service, it holds a fetchItems Method that just returns an Observable of the API Endpoint, like this:
Item-add component.ts:
#Component({
selector: 'app-item-add',
templateUrl: './item-add.component.html',
styleUrls: ['./item-add.component.css']
})
export class ItemAddComponent implements OnInit {
constructor(private itemService: ItemService, private route: ActivatedRoute, private router: Router) { }
ngOnInit(): void {
}
onCreatePost(item: Item) {
// Send Http request
this.itemService.createAndStorePost(item.description, item.isComplete);
//Here I want that the items[] in the items.list component refreshes when I add new Item
this.onCancel();
}
onCancel() {
this.router.navigate([''], {relativeTo: this.route});
}
}
And the item.service.ts:
#Injectable()
export class ItemService {
constructor(private http: HttpClient, private route: ActivatedRoute, private router: Router) {
}
fetchItems(): Observable<Item[]> {
return this.http.get<Item[]>('https://localhost:44321/api/TodoItems');
}
fetchItem(id: number): Observable<Item> {
return this.http.get<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
createAndStorePost(description: string, isComplete: boolean) {
var item = { description: description, isComplete: isComplete };
this.http.post('https://localhost:44321/api/TodoItems', item)
.subscribe(Response => {
});
}
deleteItem(id: number): Observable<Item> {
return this.http.delete<Item>('https://localhost:44321/api/TodoItems' + '/' + id);
}
updateItem(id:number, item: Item) {
this.http.put<Item>('https://localhost:44321/api/TodoItems' + '/' + id, item).subscribe();
}
}
Then the items-list component catches that Observable and subscribes to it and sets the Response from that subscription to and items[] in the component itself:
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
constructor(private route: ActivatedRoute, private router: Router, private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
});
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
}
What can I do to trigger that the items.list should fetch Items again?
I can't use #ViewChild because it is no Parent-Child relation.
Can I implement and instance of item.list anywhere in the project and just call the onFetchItems Method?
Thanks!
you can use BehaviorSubject to share data between your different components.
Here is an example:
In your ItemService.
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class ItemService {
private _itemsSource = new BehaviorSubject([]);
currentItems = this._itemsSource.asObservable();
constructor() { }
updateItems(items: []): void {
this._itemsSource.next(items)
}
}
In your ItemsComponent, you update the new value in the service after you get all the items,
#Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css']
})
export class ItemComponent implements OnInit {
items: Item[] = [];
constructor(private itemService: ItemService) { }
ngOnInit(): void {
this.onFetchItems();
}
onFetchItems() {
this.itemService.fetchItems().subscribe(Response => {
this.items = Response;
this.updateItems(this.items)
});
}
updateItems(newItems: []): void {
this.itemService.updateItems(newItems)
}
}
And in your ItemListComponent
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit {
items: Item[] = [];
subscription: Subscription;
constructor(private route: ActivatedRoute,
private router: Router,
private itemService: ItemService) { }
ngOnInit(): void {
this.subscription = this.itemService.currentItems.subscribe(items => this.items = items)
}
onNewItem() {
this.router.navigate(['new'], {relativeTo: this.route});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}

How to fix "Cannot read property '...' of undefined" when calling angular service method inside template

I created a new service in my project and I added some functions to it.
When I tried to call a service function from my component template I got this error "Cannot read property 'isCompanyEligible' of undefined"
I tried to create new function inside my component and assign the service function to it but I got the same error.
This is my service:
import { FinancialHealth } from 'src/app/shared/models/financial-health';
import { LocalStoreService } from 'src/app/shared/services/local-store.service';
import {Application} from './../models/application';
import {Injectable} from '#angular/core';
import { NgbDateParserFormatterService} from './ngb-date-parser-formatter.service ';
#Injectable({
providedIn: 'root'
})
export class EligibilityService {
application: Application;
activityAreas = [];
areasEligibility = [];
legalForms = [];
jobPositions = [];
constructor(
private ls: LocalStoreService,
private dateService: NgbDateParserFormatterService
) {
this.application = this.ls.getItem('application');
const {
activity_areas,
legal_forms,
job_positions,
areas_eligiblity
} =
this.ls.getItem('shared_data').data;
this.activityAreas = activity_areas;
this.legalForms = legal_forms;
this.jobPositions = job_positions.filter(job => job.is_management_position ==
1);
this.areasEligibility = areas_eligiblity;
}
public isCompanyEligible(application ? ) {
if (application) {
this.application = application;
}
if (!this.application || (!this.application.company)) {
return null;
}
const company = this.application.company;
let age;
if (typeof this.application.company.established_at == 'object') {
const date =
this.dateService.format(this.application.company.established_at);
age = this.getAge(date);
} else {
age = this.getAge(company.established_at)
}
return this.legalForms.includes(company.legal_form) && (age >= 2 && age <=
5);
}
growthRate(firstYear, secondYear) {
if (!firstYear || !secondYear) {
return 0;
}
return Math.round(((secondYear - firstYear) / firstYear) * 100);
}
}
This is my component.ts:
import { Component, OnInit } from '#angular/core';
import { CustomValidators } from 'ng2-validation';
import { FormGroup, FormBuilder, FormControl } from '#angular/forms';
import { ToastrService } from 'ngx-toastr';
import { DataLayerService } from 'src/app/shared/services/data-layer.service';
import { BreadcrumbService } from '../../../shared/services/breadcrumb.service';
import { EligibilityService } from 'src/app/shared/services/eligibility.service';
#Component({
selector: 'app-form-sommaire',
templateUrl: './sommaire.component.html',
styleUrls: ['./sommaire.component.scss']
})
export class SommaireFormComponent implements OnInit {
formBasic: FormGroup;
loading: boolean;
radioGroup: FormGroup;
sharedData: any;
isValid: Boolean = false;
application: any;
breadcrumb: { label: string; route: string; }[];
title: String = 'Sommaire';
constructor(
private fb: FormBuilder,
private toastr: ToastrService,
private ls: LocalStoreService,
private appService: ApplicationService,
private data: BreadcrumbService,
public eligibility: EligibilityService
) { }
}
This is my HTML template:
<div class="col-lg-2">
<i *ngIf="eligibility.isCompanyEligible()" class="icon ion-ios-checkmark-circle large-success"></i>
<i *ngIf="eligibility.isCompanyEligible() === false" class="icon ion-ios-close-circle large-danger"></i>
<i *ngIf="eligibility.isCompanyEligible() == null" class="icon ion-md-alert large-warning"></i>
</div>
Anything marked as private cannot be accessed by the component's template either. (Private members can be accessed when using JIT, such as at development time, but not when using AOT, such as for production.)
Actually, best practice is to wrap any service properties/methods in a component property/method and have the template bind to/call the component's property or method to access the service data.
Something like this:
get isCompanyEligible(): boolean {
return this.eligibility.isCompanyEligible();
}
and use it in your template: - <i *ngIf="isCompanyEligible()"
OR
Make the EligibilityService injection public in constructor of component, to access inside template:
constructor(
private fb: FormBuilder,
private toastr: ToastrService,
private ls: LocalStoreService,
private appService: ApplicationService,
private data: BreadcrumbService,
public eligibility: EligibilityService
) { }

Angular 4: reactive form control is stuck in pending state with a custom async validator

I am building an Angular 4 app that requires the BriteVerify email validation on form fields in several components. I am trying to implement this validation as a custom async validator that I can use with reactive forms. Currently, I can get the API response, but the control status is stuck in pending state. I get no errors so I am a bit confused. Please tell me what I am doing wrong. Here is my code.
Component
import { Component,
OnInit } from '#angular/core';
import { FormBuilder,
FormGroup,
FormControl,
Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EmailValidationService } from '../services/email-validation.service';
import { CustomValidators } from '../utilities/custom-validators/custom-validators';
#Component({
templateUrl: './email-form.component.html',
styleUrls: ['./email-form.component.sass']
})
export class EmailFormComponent implements OnInit {
public emailForm: FormGroup;
public formSubmitted: Boolean;
public emailSent: Boolean;
constructor(
private router: Router,
private builder: FormBuilder,
private service: EmailValidationService
) { }
ngOnInit() {
this.formSubmitted = false;
this.emailForm = this.builder.group({
email: [ '', [ Validators.required ], [ CustomValidators.briteVerifyValidator(this.service) ] ]
});
}
get email() {
return this.emailForm.get('email');
}
// rest of logic
}
Validator class
import { AbstractControl } from '#angular/forms';
import { EmailValidationService } from '../../services/email-validation.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
export class CustomValidators {
static briteVerifyValidator(service: EmailValidationService) {
return (control: AbstractControl) => {
if (!control.valueChanges) {
return Observable.of(null);
} else {
return control.valueChanges
.debounceTime(1000)
.distinctUntilChanged()
.switchMap(value => service.validateEmail(value))
.map(data => {
return data.status === 'invalid' ? { invalid: true } : null;
});
}
}
}
}
Service
import { Injectable } from '#angular/core';
import { HttpClient,
HttpParams } from '#angular/common/http';
interface EmailValidationResponse {
address: string,
account: string,
domain: string,
status: string,
connected: string,
disposable: boolean,
role_address: boolean,
error_code?: string,
error?: string,
duration: number
}
#Injectable()
export class EmailValidationService {
public emailValidationUrl = 'https://briteverifyendpoint.com';
constructor(
private http: HttpClient
) { }
validateEmail(value) {
let params = new HttpParams();
params = params.append('address', value);
return this.http.get<EmailValidationResponse>(this.emailValidationUrl, {
params: params
});
}
}
Template (just form)
<form class="email-form" [formGroup]="emailForm" (ngSubmit)="sendEmail()">
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<fieldset class="form-group required" [ngClass]="{ 'has-error': email.invalid && formSubmitted }">
<div>{{ email.status }}</div>
<label class="control-label" for="email">Email</label>
<input class="form-control input-lg" name="email" id="email" formControlName="email">
<ng-container *ngIf="email.invalid && formSubmitted">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Please enter valid email address.
</ng-container>
</fieldset>
<button type="submit" class="btn btn-primary btn-lg btn-block">Send</button>
</div>
</div>
</form>
There's a gotcha!
That is, your observable never completes...
This is happening because the observable never completes, so Angular does not know when to change the form status. So remember your observable must to complete.
You can accomplish this in many ways, for example, you can call the first() method, or if you are creating your own observable, you can call the complete method on the observer.
So you can use first()
UPDATE TO RXJS 6:
briteVerifyValidator(service: Service) {
return (control: AbstractControl) => {
if (!control.valueChanges) {
return of(null);
} else {
return control.valueChanges.pipe(
debounceTime(1000),
distinctUntilChanged(),
switchMap(value => service.getData(value)),
map(data => {
return data.status === 'invalid' ? { invalid: true } : null;
})
).pipe(first())
}
}
}
A slightly modified validator, i.e always returns error: STACKBLITZ
OLD:
.map(data => {
return data.status === 'invalid' ? { invalid: true } : null;
})
.first();
A slightly modified validator, i.e always returns error: STACKBLITZ
So what I did was to throw a 404 when the username was not taken and use the subscribe error path to resolve for null, and when I did get a response I resolved with an error. Another way would be to return a data property either filled width the username or empty
through the response object and use that insead of the 404
Ex.
In this example I bind (this) to be able to use my service inside the validator function
An extract of my component class ngOnInit()
//signup.component.ts
constructor(
private authService: AuthServic //this will be included with bind(this)
) {
ngOnInit() {
this.user = new FormGroup(
{
email: new FormControl("", Validators.required),
username: new FormControl(
"",
Validators.required,
CustomUserValidators.usernameUniqueValidator.bind(this) //the whole class
),
password: new FormControl("", Validators.required),
},
{ updateOn: "blur" });
}
An extract from my validator class
//user.validator.ts
...
static async usernameUniqueValidator(
control: FormControl
): Promise<ValidationErrors | null> {
let controlBind = this as any;
let authService = controlBind.authService as AuthService;
//I just added types to be able to get my functions as I type
return new Promise(resolve => {
if (control.value == "") {
resolve(null);
} else {
authService.checkUsername(control.value).subscribe(
() => {
resolve({
usernameExists: {
valid: false
}
});
},
() => {
resolve(null);
}
);
}
});
...
I've been doing it slightly differently and faced the same issue.
Here is my code and the fix in case if someone would need it:
forbiddenNames(control: FormControl): Promise<any> | Observable<any> {
const promise = new Promise<any>((resolve, reject) => {
setTimeout(() => {
if (control.value.toUpperCase() === 'TEST') {
resolve({'nameIsForbidden': true});
} else {
return null;//HERE YOU SHOULD RETURN resolve(null) instead of just null
}
}, 1);
});
return promise;
}
I tries using the .first(). technique described by #AT82 but I didn't find it solved the problem.
What I eventually discovered was that the form status was changing but it because I'm using onPush, the status change wasn't triggering change detection so nothing was updating in the page.
The solution I ended up going with was:
export class EmailFormComponent implements OnInit {
...
constructor(
...
private changeDetector: ChangeDetectorRef,
) {
...
// Subscribe to status changes on the form
// and use the statusChange to trigger changeDetection
this.myForm.statusChanges.pipe(
distinctUntilChanged()
).subscribe(() => this.changeDetector.markForCheck())
}
}
import { Component,
OnInit } from '#angular/core';
import { FormBuilder,
FormGroup,
FormControl,
Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { EmailValidationService } from '../services/email-validation.service';
import { CustomValidators } from '../utilities/custom-validators/custom-validators';
#Component({
templateUrl: './email-form.component.html',
styleUrls: ['./email-form.component.sass']
})
export class EmailFormComponent implements OnInit {
public emailForm: FormGroup;
public formSubmitted: Boolean;
public emailSent: Boolean;
constructor(
private router: Router,
private builder: FormBuilder,
private service: EmailValidationService
) { }
ngOnInit() {
this.formSubmitted = false;
this.emailForm = this.builder.group({
email: [ '', [ Validators.required ], [ CustomValidators.briteVerifyValidator(this.service) ] ]
});
}
get email() {
return this.emailForm.get('email');
}
// rest of logic
}

Angular2 : access method of a class in template

I have the following class in an Angular2 app
export class Contact {
constructor(
public has_reply: boolean,
public archived: boolean
) { }
getStatus() : string {
if (this.archived) {
return "Archived";
}
if (this.has_reply) {
return "Answered";
}
return "Waiting";
}
}
which is returned by a service
#Injectable()
export class ContactsService {
private contactsData : BehaviorSubject<Array<Contact>> = null;
constructor(private http: Http) {
this.contactsData = new BehaviorSubject<Array<Contact>>([]);
}
/**
* get the list of contacts
*/
populateContacts() : Array<Contact> {
return this.http.get('/api/contacts/').map(
(res: Response) => {return res.json()}
).subscribe(
jsonData => {
this.contactsData.next(<Array<Contact>> jsonData);
}
);
}
onContactsChanged() : Observable<Array<Contact>>{
return this.contactsData.asObservable();
}
}
which is used in a component
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
private contacts: Array<Contact> = [];
constructor(
private contactsApi : ContactsService
) { }
ngOnInit() {
this.contactsApi.onContactsChanged().subscribe(
(contacts: Array<Contact>) => {this.contacts = contacts;}
);
this.contactsApi.populateContacts();
}
}
and displayed in a template
<table class="table table-striped table-bordered">
<tr *ngFor="let contact of contacts">
<td>
{{ contact.getStatus() }}
</td>
</tr>
I get the following error
EXCEPTION: Error in ./HomeComponent class HomeComponent - inline
template:11:8 caused by: self.context.$implicit.getStatus is not a function
What is wrong in my approach? Does Angular2 allow to call a class method like this?
Note : Calling method from a Angular 2 class inside template looks similar question but it did not help
As suggested by #AdnanA, the problem is a casting issue. See How to do runtime type casting in TypeScript?
I fixed by casting each object of the array: See https://stackoverflow.com/a/32186367/117092
// Thank you! https://stackoverflow.com/a/32186367/117092
function cast<T>(obj, cl): T {
obj.__proto__ = cl.prototype;
return obj;
}
#Injectable()
export class ContactsService {
private contactsData : BehaviorSubject<Array<Contact>> = null;
constructor(private http: Http) {
this.contactsData = new BehaviorSubject<Array<Contact>>([]);
}
/**
* get the list of contacts
*/
populateContacts() : Array<Contact> {
return this.http.get('/api/contacts/').map(
(res: Response) => {return res.json()}
).subscribe(
jsonData => {
// WRONG! this.contactsData.next(<Array<Contact>> jsonData);
// FIXED BY
let contactsArray: Array<Contact> = [];
for (let i=0, l=jsonData.length; i<l; i++) {
let contact = cast<Contact>(jsonData[i], Contact);
contactsArray.push(contact);
}
this.contactsData.next(contactsArray);
}
);
}
onContactsChanged() : Observable<Array<Contact>>{
return this.contactsData.asObservable();
}
}
If the data is acquired async you need to guard against null
{{ contact?.getStatus() }}

Categories

Resources