Angular2 : access method of a class in template - javascript

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() }}

Related

Messages disappearing after refreshing the page

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.

Angular creating filter with pipe and map

I am fairly new to angular and I am trying to create a filter for a value.
In my component - I have => myData$: Observable<MyInterface>
and the interface is as follows
export class FoundValues {
customerName: string;
startDate: string;
endDate: string;
includes(values: string) : boolean {
value = value.toLowerCase();
return this.valueIncludes(this.customerName, value);
}
private valueIncludes(includedValue, value){
if (value) {
const value = value.toLowerCase().includes(includedValue);
return result;
} else {
return false;
}
}
}
export interface MyInterface {
found_values : Array<FoundValues>;
}
In my component ngOnInit(), I am trying to create a logic filter but not getting it as it return a type FoundValues[] and it's complaining that it's not the expected Observable return type.
export class MyComponent implements OnInit{
myData$ = Observable<MyInterface>;
myControl = new FormControl();
ngOnInit(): void{
this.filterData =
this.myControl.valueChanges.pipe(map(value=>this._filter(value)));
}
private _filter(value:string): .....{
--- need logic here ----
}
}
How can I create the filter so that if I type a customer name in my form it shows only the matching customer name?
You can use the combineLatest RxJS operator for filtering through as shown in the following code snippet,
export class MyComponent implements OnInit {
myData$ = Observable < MyInterface > ;
mySearchQuery$: Observable < any > ;
searchString = new FormControl('');
filterData: Observable < any >
constructor() {
mySearchQuery$ = this.searchString.valueChanges.startsWith('');
}
ngOnInit(): void {
this.filterData = this.searchQuery$.combineLatest(this.myData$).map(([queryString, listOfCustomers]) => {
return listOfCustomers.filter(customer => customer.name.toLowerCase().indexOf(queryString.toLowerCase()) !== -1)
})
}
}
The combineLatest RxJS operator takes in the observables myData$ and mySearchQuery and returns the observable filterData containing the emitted values that match the searchString.
usual design in angular would be different
https://stackblitz.com/edit/angular7-rxjs-pgvqo5?file=src/app/app.component.ts
interface Entity {
name: string;
//...other properties
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
name = new FormControl('');
data$: Observable<Array<Entity>> = of([
{ name: 'Jhon' },
{ name: 'Jane' },
{ name: 'Apple' },
{ name: 'Cherry' },
]);
filtered$: Observable<Array<Entity>>;
ngOnInit() {
// this can be moved to a util lib/file
const startWithPipe = pipe(
map(
([query, data]: [query: string, data: Array<Entity>]): Array<Entity> =>
data.filter((entity) =>
query ? entity.name.toLowerCase().startsWith(query) : true
)
)
);
this.filtered$ = this.name.valueChanges.pipe(
startWith(''),
debounceTime<string>(300),
withLatestFrom(this.data$),
startWithPipe
);
}

how can i find paramter of previous route in angular

i want find the params in previous route in angular typescript .
i use this code :
private previousUrl: string = undefined;
private currentUrl: string = undefined;
constructor(private router: Router) {
this.currentUrl = this.router.url;
router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.previousUrl = event.url;
this.currentUrl = this.currentUrl;
}
});
}
but i can not access to the params of this url :
http://localhost:4200/claims-manager/200/edit
i want ti access 200 . how can i find params in url ????
You can do it in your component file but It is a best practice to do it in a service (using rxjs) to pass data and call it in your component file
In your service
export class myService {
constructor() { }
private param = new BehaviorSubject("");
sharedParam = this.param.asObservable();
paramToPass(param:string) {
this.param.next(param)}
}
In your component class that set param
export class ComponentSetParam {
param: string
constructor(private myService: Service)
this.myService.setParam(this.param);
}
in your appModule
#NgModule({
declarations: [YourComponents]
imports: [ AppRoutingModule, YourModules...],
providers: [ShareService],
})
export class AppModule {}
Component that you want to pass data
export class ComponentGetParam {
paramFromService: string
constructor(private myService: Service) {
this.shareService.sharedData.subscribe(data : string => {
this.paramFromService = data;
})
}
}
Try this:
readonly _destroy$: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
constructor(
private activatedRoute: ActivatedRoute,
) {
this.activatedRoute.parent.paramMap
.pipe(
distinctUntilChanged(),
takeUntil(this._destroy$)
)
.subscribe((params: ParamMap) => {
const id = params.get('id');
});
}
ngOnDestroy() {
this._destroy$.next(true);
this._destroy$.complete();
}
Where 'id' is a name, that you use in the routing, e.g.
path: '/claims-manager/:id/'
Demo You can do it in service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class ShareService {
constructor() { }
private paramSource = new BehaviorSubject("");
sharedData = this.paramSource.asObservable();
setParam(param:string) { this.paramSource.next(param)}
}
in constructors
constructor(private shareService: ShareService)
in component in ngOnDestroy set this like this.shareService.setParam(param);
in appmodule
providers:[ShareService ]
in new component in ngOnInit or in constructor get like
this.shareService.sharedData.subscribe(data=> { console.log(data); })

Class function inside a component while running ngFor gives Error (not a function)

I'm trying to show a list of topics where every list entry is a custom component.
I will describe my problem as a simple example of my code.
Using the current (02/2020) Version of Angular, MongoDB and Chrome
Topic class:
export class Topic {
constructor(
public title: string,
public solutionID: number[] = [],
private rating: number = 0,
private votes: number = 0
) { }
currentRating(): number {
return this.rating / this.votes;
}
vote(stars: number) {
this.votes++;
this.rating += stars;
}
lastEditDate(): Date {
console.log('test');
return this.ts_worker[this.ts_worker.length - 1];
}
}
main-view.component.html This is the "frame" where the list is shown
<div class="content-wrapper">
<app-topic-view *ngFor="let tp of topics" [topic]="tp"></app-topic-view>
</div>
main-view.component.ts This is where my topics come from (GET from Server)
import { Component, OnInit, Input } from '#angular/core';
import { TopicsService } from 'src/app/services/topics.service';
import { Topic } from 'src/app/classes/class_Topic';
#Component({
selector: 'app-main-view',
templateUrl: './main-view.component.html',
styleUrls: ['./main-view.component.scss']
})
export class MainViewComponent implements OnInit {
#Input() topics: Topic[];
constructor(private topicService: TopicsService) { }
ngOnInit() {
this.topicService.getAllTopics().subscribe((topics: Topic[]) => {
this.topics = topics;
})
}
}
topic-view.component.html
<div class="topicElement">
<!-- Some code hidden here -->
<div class="back-group">
<div class="solutionCount">Solutions: {{(topic.ts_worker[topic.ts_worker.length - 1])}}</div>
<div class="solutionCount">Solutions: {{(topic.lastEditDate())}}</div>
</div>
</div>
The error is found in {{(topic.lastEditDate()}}.
The line above that works just fine. Only the function call doesn't work.
Error
Goal
What am I missing here
In the end I would like to use the functions of my class. I'm used to do this in other languages.
Is this possible in Angular?
EDIT: Typo fixed
your "topics" are not class Topic when return of the http because when it's traspile to javaScript, you only has an object, you need create as
this.topicService.getAllTopics().subscribe((topics: Topic[]) => {
this.topics=x.map(x=>new Topic(x.title,x.solutionID,x.rating,x.votes))
})
Others idea is that Topic was
export class Topic {
public title;
public solutionID;
private rating;
private votes;
constructor({ title, solutionID, rating, votes }) {
this.title = title;
this.solutionID = solutionID;
this.rating = rating;
this.votes = votes;
}
..your methods...
}
And write
this.topicService.getAllTopics().subscribe((topics: Topic[]) => {
this.topics=x.map(x=>new Topic(x))
})
And another idea are that in your app-topic-view you has
private _topic:Topic
#Input() set topic(value)
{
this._topic=new Topic(value.title,value.solutionID,value.rating,value.votes)
//or if you change the constructor
// this._topic=new Topic(value);
}
get topic()
{
return this._topic;
}

Angular 2 Error while making get request: TS2346: Supplied parameters do not match any signature of call target

I am trying to make a simple get request following the angular docs: https://angular.io/docs/ts/latest/guide/server-communication.html
Still getting this error on line 15 in editor.
return this.http.get(this.heroesUrl)
.map(this.extractData)
Please find full code here:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
#Injectable()
export class EventListService {
private heroesUrl = '/data.json'; // URL to web API
constructor (private http: Http) {}
getEvents (): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response): Hero[] {
let body = res.json();
return body.data || { } as Hero[];
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
interface Hero {
}
Calling the getEvents method:
import { Component, OnInit } from '#angular/core';
import { EventListService } from '../services/event-list.service';
#Component({
selector: "event-list",
templateUrl: "./event-list/event-list.component.html",
styleUrls: ["./event-list/event-list.component.css"],
providers: [ EventListService ]
})
export class EventListComponent implements OnInit{
showImage: boolean = true;
searchString: string = "";
eventList: any[] = [];
constructor(private eventListService: EventListService) {
eventListService.getEvents();
}
ngOnInit() {
console.error("INIT");
}
toggleImage():void {
this.showImage = !this.showImage;
}
ratingClicked(data) {
console.error(data);
}
}
Your error could be here:
getEvents (): Observable<Hero[]> {
return this.http.get(this.heroesUrl)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response): Hero[] {
...
}
You see the missmatch. getEvents is returning an Observable of Hero Array, but your extractData is returning a Hero Array.
Just change your extractData to:
private extractData(res: Response) {
let body = res.json();
return body.data || { }
}
and I would suggest you move the call of getEvents to ngOnInit instead of the constructor, as per reference here, so do this instead:
ngOnInit() {
this.eventListService.getEvents()
.subscribe(d => {
this.eventList = d})
}
and eventList should be:
eventList: Hero[] = [];
Just subscribe to the service call, that is why it's not doing the request.
Change this:
export class EventListComponent implements OnInit{
showImage: boolean = true;
searchString: string = "";
eventList: any[] = [];
constructor(private eventListService: EventListService) {
eventListService.getEvents();
}
ngOnInit() {
console.error("INIT");
}
toggleImage():void {
this.showImage = !this.showImage;
}
ratingClicked(data) {
console.error(data);
}
}
For this:
export class EventListComponent implements OnInit{
showImage: boolean = true;
searchString: string = "";
eventList: any[] = [];
constructor(private eventListService: EventListService) {
eventListService.getEvents().subscribe((response:Hero[]) => {
console.log(response);
});
}
ngOnInit() {
console.error("INIT");
}
toggleImage():void {
this.showImage = !this.showImage;
}
ratingClicked(data) {
console.error(data);
}
}

Categories

Resources