Extend a model class by a variable - angularjs

I have a model class named user :
export class User {
constructor(
public UserId?: number,
public Name?: string,
public Password?: string,
public IsActive?: boolean,
public RoleId?: number
) {
}
}
For ng2-select component, I need these properties : text and id.
Now when I set them via
this.userService.getAllUsers().subscribe(
data => this.users = data,
err => this.error(err));
there are no text and id property.
Is there a method where I can set these properties on initialization.
I dont wan't to write everytime a workaround with :
data.forEach(g =>
{
g.text = g.Name;
g.id = g.Id;
});
this.users = data;

Since it seems you are not typing your array users to your User class anyway in the above code snippet, so you could introduce another class and where you handle this assignment in map.
export class UserSelect {
constructor(
public text: string,
public id: number
) {
}
and in your Service:
getAllUsers() {
return this.http......
.map(res => res.json().map(x => new UserSelect(x.Name, x.Id)))
}
and then you just subscribe normally
this.userService.getAllUsers()
.subscribe(data => {
this.users = data;
})
Then you would have an Array of type UserSelect which has the proper properties you need :) Of course you can extend this and include any properties you need, keeping in mind that not knowing to which extent you are using the users array and if you are needing the other properties, or original properties...

Related

ngbTypeahead search results missing

I am currently writing an ngbTypeahead search and I am stuck because I have never really worked with Observables, which are the expected return type of the ngbTypeahead search.
The search function in my component looks like this:
search: OperatorFunction<string, readonly LoqateResponseModel[]> = (text$: Observable<string>) => {
return text$.pipe(
switchMap(term => this.addressService.searchAddress(term, this.countryCode)),
map(results => {
const searchResults = results[LoqateKeyEnum.ITEMS] as [];
const searchResultLoqateModels: LoqateResponseModel[] = [];
searchResults.forEach(result => {
searchResultLoqateModels.push(new LoqateResponseModel(
result[LoqateKeyEnum.ID],
result[LoqateKeyEnum.TYPE],
result[LoqateKeyEnum.TEXT],
result[LoqateKeyEnum.HIGHLIGHT],
result[LoqateKeyEnum.DESCRIPTION]));
});
return searchResultLoqateModels;
})
);
}
resultFormatter = (loqateResponse: LoqateResponseModel): string => loqateResponse.display();
I am conducting a loqate search and am storing the results as model objects in a list and return them.
public searchAddress(searchValue, countryCode): Observable<object>
{
const httpParams = new HttpParams();
return this.httpClient.post(this.addressSearchUrl, {}, {
headers: this.headers,
params: new HttpParams()
.set('Key', loqateKey)
.set('Text', searchValue)
.set('Origin', countryCode)
});
}
The Model looks like this:
export class LoqateResponseModel {
constructor(
public id: string,
public type: LoqateTypeEnum,
public text: string,
public highlight: string,
public description: string) {
}
public isAddress(): boolean { return this.type === LoqateTypeEnum.ADDRESS; }
public display(): string { return this.text + ', ' + this.description; }
}
Now I thought, that a list of LoqateResponseModels is stored as result of the search and then each of these list items are being formatted properly to display in the typeahead popup through the resultFormatter.
tldr: I want to search something with the ngbTypeahead and query the search term from an API endpoint and display the search results in the typeahead popup.
Edit: I've edited the answer, this code is working.
I think you are looking for switchMap. This operator will subscribe you an observable source and emit its results:
You don't want to return null, you simply return the observable with your piped modification.
Your operator function should take an observable and return an observable. Your observable can simply use switchMap to map the incoming text to the api call.
search: OperatorFunction<string, readonly LoqateResponseModel[]> = text$ =>
text$.pipe(
switchMap(text => this.searchAddress(text))
);
Each time switchMap receives some text, it will do a few things for you:
subscribe to searchAddress(text)
emit results from this subscription
stop emitting results from previous subscriptions

ERROR TypeError: Cannot read property 'details' of undefined

I have a plain text on local server that i want to display on my web page using angular. I have my model, service and component.
So I am getting an error at this line >> this.paragraphs = this.announcement.details.split('#');
I tried using ? operator (this.paragraphs = this.announcement?.details.split('#')) but it could not build.
MODEL
export class Announcements
{
public id: number;
public details: string;
public date: Date;
}
SERVICE
getAnnouncementById(id)
{
return this.http.get<Announcements>('http://localhost:49674/api/Announcements/' + id)
.pipe(catchError(this.errorHandler));
}
COMPONENT-.ts
import { Announcements } from '../models/Announcement';
export class ReadMoreComponent implements OnInit
{
public announcementId;
public announcement : Announcements
public paragraphs = [];
constructor(
private route: ActivatedRoute,
private router:Router,
private announcementservice: AnnouncementsService
){}
ngOnInit()
{
this.route.paramMap.subscribe((params:ParamMap) => {
let id = parseInt(params.get('id'));
this.announcementId = id;
this.getAnnouncenentById(id)
//split
this.paragraphs = this.announcement.details.split('#');
})
}
getAnnouncenentById(id){
this.announcementservice.getAnnouncementById(id)
.subscribe(data => this.announcement = data);
}
COMPONENT-.html
<div class="article column full">
<div *ngFor=" let paragraph of paragraphs">
<p>{{paragraph.details}}</p>
</div>
</div>
this.paragraphs = this.announcement.details.split('#'); is called before this.announcement = data so this.annoucement is undefined in that moment.
To be sure that both values already comes form observables you can use combineLatest function or switchMap operator.
Adding ? operator is workaround. Your observable still can call with unexpected order.
e.g.:
this.route.paramMap.pipe(switchMap((params: ParamMap) => {
let id = parseInt(params.get('id'));
this.announcementId = id;
this.getAnnouncenentById(id);
return this.announcementservice.getAnnouncementById(id)
})).subscribe((data) => {
this.announcement = data
this.paragraphs = this.announcement.details.split('#');
});
In that code subscription will start after first observable emits value.

typescript - return specific class instance from mixed array using typed function

Is it possible to retrieve specific type from mixed array using typed function?
public plugins: (Tool|Service)[] = [];
getTools(): Tool[]{
return this.plugins.filter(t => t instanceof Tool);
}
So far I have no luck. Typescript is throwing following message
TS2322: Type '(Tool | Service)[]' is not assignable to type 'Tool[]'. Property 'onclick' is missing in type 'Service'.
Is there any way how I can set function type to Tool[] here?
Here is full code:
interface Required {
id: string
title: string
owner: string
type: 'user' | 'admin'
}
class P {
id; title; owner; type;
constructor(config: Required){
this.id = config.id || 'uniqid';
this.title = config.title || 'Title';
this.owner = config.owner || 'user';
this.type = config.type;
}
}
interface ToolRequired extends Required{
onclick: () => void
}
class Tool extends P {
onclick;
constructor(config = {} as ToolRequired){
super(config);
this.type = 'tool';
this.onclick = config.onclick
}
}
class Service extends P {
constructor(config = {} as Required){
super(config);
this.type = 'service'
}
}
class Storag {
static types = {
tool: Tool,
service: Service,
undefined: Tool,
};
public plugins: (Tool|Service)[] = [];
setPlugin(config = {} as Required){
const Type = Storag.types[config.type];
this.plugins.push( new Type(config) );
}
getTools(): Tool[]{
return this.plugins.filter(t => t instanceof Tool);
}
}
Just tack on an as Tool[] at the end.
public plugins: (Tool|Service)[] = [];
getTools(): Tool[]{
return this.plugins.filter(t => t instanceof Tool) as Tool[]; // << here
}
The reason you need to do this is because the Typescript compiler isn't smart enough to know that when you do such a filter it will only return Tools. A .filter on any array will usually return the same type as the previous array, which is what the compiler assumes here - a Tool|Service array.
The compiler is smart enough to know, however, that a Tool|Service can be reduced down to only Tools - as such, you can do an as Tool[] at the end to tell the compiler I know what I'm doing - the type that ".filter" returns will only be Tools here, and the compiler will listen and respect it as such.
You can read more about the as keyword here: https://www.typescriptlang.org/docs/handbook/basic-types.html (scroll down or search for "Type assertions").

angular2 observables filter each array element than return it to an array

I have an array of Threads Objects with ID, title and a isBookmarked:boolean.
I've created a Subject and I want to subscribe to it in order to get an array of Thread Objects with isBookmarked=true.
https://plnkr.co/edit/IFGiM8KoSYW6G0kjGDdY?p=preview
Inside the Service I have
export class Service {
threadlist:Thread[] = [
new Thread(1,'Thread 1',false),
new Thread(2,'Thread 2',true),
new Thread(3,'Thread 3',false),
new Thread(4,'Thread 4',true),
new Thread(5,'Thread 5',true),
new Thread(6,'Thread 6',false),
new Thread(7,'Thread 7',false),
]
threadlist$:Subject<Thread[]> = new Subject<Thread[]>()
update() {
this.threadlist$.next(this.threadlist)
}
}
in the component
export class AppComponent implements OnInit {
localThreadlist:Thread[];
localThreadlistFiltered:Thread[];
constructor(private _service:Service){}
ngOnInit():any{
//This updates the view with the full list
this._service.threadlist$.subscribe( threadlist => {
this.localThreadlist = threadlist;
})
//here only if isBookmarked = true
this._service.threadlist$
.from(threadlist)//????
.filter(thread => thread.isBookmarked == true)
.toArray()
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
})
}
update() {
this._service.update();
}
}
which Instance Method do I use in general to split an array?
Also is there a better way to do it?
Thanks
You would leverage the filter method of JavaScript array within the map operator of observables:
this._service.threadlist$
.map((threads) => {
return threads.filter((thead) => thread.isBookmarked);
})
.subscribe( threadlist => {
this.localThreadlistFiltered = threadlist;
});
See this plunkr: https://plnkr.co/edit/COaal3rLHnLJX4QmvkqC?p=preview.

Angular 2 observable doesn't 'map' to model

As I'm learning Angular 2 I used an observable to fetch some data via an API. Like this:
getPosts() {
return this.http.get(this._postsUrl)
.map(res => <Post[]>res.json())
.catch(this.handleError);
}
My post model looks is this:
export class Post {
constructor(
public title: string,
public content: string,
public img: string = 'test') {
}
The problem I'm facing is that the map operator doesn't do anything with the Post model. For example, I tried setting a default value for the img value but in the view post.img displays nothing. I even changed Post[] with an other model (Message[]) and the behaviour doesn't change. Can anybody explain this behaviour?
I had a similar issue when I wanted to use a computed property in a template.
I found a good solution in this article:
http://chariotsolutions.com/blog/post/angular-2-beta-0-somnambulant-inauguration-lands-small-app-rxjs-typescript/
You create a static method on your model that takes an array of objects and then call that method from the mapping function. In the static method you can then either call the constructor you've already defined or use a copy constructor:
Mapping Method
getPosts() {
return this.http.get(this._postsUrl)
.map(res => Post.fromJSONArray(res.json()))
.catch(this.handleError);
}
Existing Constructor
export class Post {
// Existing constructor.
constructor(public title:string, public content:string, public img:string = 'test') {}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj['title'], obj['content'], obj['img']));
}
}
Copy Constructor
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Object) {
this.title = obj['title'];
this.content = obj['content'];
this.img = obj['img'] || 'test';
}
// New static method.
static fromJSONArray(array: Array<Object>): Post[] {
return array.map(obj => new Post(obj);
}
}
If you're using an editor that supports code completion, you can change the type of the obj and array parameters to Post:
export class Post {
title:string;
content:string;
img:string;
// Copy constructor.
constructor(obj: Post) {
this.title = obj.title;
this.content = obj.content;
this.img = obj.img || 'test';
}
// New static method.
static fromJSONArray(array: Array<Post>): Post[] {
return array.map(obj => new Post(obj);
}
}
You can use the as keyword to de-serialize the JSON to your object.
The Angular2 docs have a tutorial that walks you through this. However in short...
Model:
export class Hero {
id: number;
name: string;
}
Service:
...
import { Hero } from './hero';
...
get(): Observable<Hero> {
return this.http
.get('/myhero.json')
.map((r: Response) => r.json() as Hero);
}
Component:
get(id: string) {
this.myService.get()
.subscribe(
hero => {
console.log(hero);
},
error => console.log(error)
);
}

Resources