Angular2 Async Data Issue - asynchronous

I have a component that is reliant upon an asynchronously fetched object. There are also child components in the template that rely on some data from this same object.
The problem I am having seems to be a classic race condition, but I'm not familiar enough with Angular 2 to understand the solution.
Take this for instance:
export class SampleComponent {
constructor(service: SomeService) {
this.service = service;
this._loadData();
}
private _loadData() {
this.service.getData().subscribe(data => this.data = data);
}
}
But in the template, I have child components to display certain parts of this.data:
<taglist tags="data?.tags"></taglist>
Now the Component for taglist looks something like:
#Component({
selector: 'taglist',
directives: [NgFor],
inputs: ['tags'],
template: `<span *ngFor="#tag of tags">{{ tag }}</span>`
})
export class TagList {
public tags: Array<string> = [];
constructor() {
//
}
}
Because tags input is received from an async loaded dataset, its not present when the Tag Component is initialized. What can I do so that when this.data load is finished, the sub components that use it will automatically access the newly loaded data?
Thank you for any insight you may be able to provide me!

Implement ngOnChanges() (https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html)
#Component({
selector: 'taglist',
inputs: ['tags'],
template: `<span *ngFor="let tag of tags">{{ tag }}</span>`
})
export class TagList {
public tags: Array<string> = [];
constructor() {
//
}
ngOnChanges(changes: {[propName: string]: SimpleChange}) {
console.log('ngOnChanges - tags = ' + changes['tags'].currentValue);
}
}
For angular to handle the binding also use
<taglist [tags]="data?.tags"></taglist> or <taglist tags="{{data?.tags}}"></taglist>
otherwise it's a simple string to attribute assignment Angular doesn't handle.

This is the usual use case for the built-in async pipe, that is meant to be used to easily consume observables.
Try the following, first create a getter than returns directly the service layer observable:
get data(): Observable {
return this.service.getData();
}
Then you can consume this observable directly in the template using the async pipe:
<span *ngFor="let tag of data | async">{{ tag }}</span>

Related

nested route using #ApiParam, how to use a custom validator in nestjs?

I’m looking for some help about custom validator & custom decorator in Nest.
FIRST CASE : working one
A DTO, with class-validator anotations :
import { IsNotEmpty, IsString } from 'class-validator';
import { IsOwnerExisting } from '../decorators/is-owner-existing.decorator';
export class CreatePollDto {
#IsNotEmpty()
#IsString()
#IsOwnerExisting() // custom decorator, calling custom validator, using a service to check in db
ownerEmail: string;
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
slug: string;
}
I use it in a controller :
#Controller()
#ApiTags('/polls')
export class PollsController {
constructor(private readonly pollsService: PollsService) {}
#Post()
public async create(#Body() createPollDto: CreatePollDto): Promise<Poll> {
return await this.pollsService.create(createPollDto);
}
}
When this endpoint is called, the dto is validating by class-validator, and my custom validator works. If the email doesn’t fit any user in database, a default message is displayed.
That is how I understand it.
SECOND CASE : how to make it work ?
Now, I want to do something similar but in a nested route, with an ApiParam. I’d like to check with a custom validator if the param matches some object in database.
In that case, I can’t use a decorator in the dto, because the dto doesn’t handle the "slug" property, it’s a ManyToOne, and the property is on the other side.
// ENTITIES
export class Choice {
#ManyToOne((type) => Poll)
poll: Poll;
}
export class Poll {
#Column({ unique: true })
slug: string;
#OneToMany((type) => Choice, (choice) => choice.poll, { cascade: true, eager: true })
#JoinColumn()
choices?: Choice[];
}
// DTOs
export class CreateChoiceDto {
#IsNotEmpty()
#IsString()
label: string;
#IsOptional()
#IsString()
imageUrl?: string;
}
export class CreatePollDto {
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
slug: string;
#IsOptional()
#IsArray()
#ValidateNested({ each: true })
#Type(() => CreateChoiceDto)
choices: CreateChoiceDto[] = [];
}
So where should I hook my validation ?
I’d like to use some decorator directly in the controller. Maybe it’s not the good place, I don’t know. I could do it in the service too.
#Controller()
#ApiTags('/polls/{slug}/choices')
export class ChoicesController {
constructor(private readonly choicesService: ChoicesService) {}
#Post()
#ApiParam({ name: 'slug', type: String })
async create(#Param('slug') slug: string, #Body() createChoiceDto: CreateChoiceDto): Promise<Choice> {
return await this.choicesService.create(slug, createChoiceDto);
}
}
As in my first case, I’d like to use something like following, but in the create method of the controller.
#ValidatorConstraint({ async: true })
export class IsSlugMatchingAnyExistingPollConstraint implements ValidatorConstraintInterface {
constructor(#Inject(forwardRef(() => PollsService)) private readonly pollsService: PollsService) {}
public async validate(slug: string, args: ValidationArguments): Promise<boolean> {
return (await this.pollsService.findBySlug(slug)) ? true : false;
}
public defaultMessage(args: ValidationArguments): string {
return `No poll exists with this slug : $value. Use an existing slug, or register one.`;
}
}
Do you understand what I want to do ? Is it feasible ? What is the good way ?
Thanks a lot !
If you're needing to validate the slug with your custom rules you have one of two options
make a custom pipe that doesn't use class-validator and does the validation directly in it.
Use #Param() { slug }: CreatePollDto. This assumes that everything will be sent via URL parameters. You could always make the DTO a simple one such as
export class SlugDto {
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
slug: string;
}
And then use #Param() { slug }: SlugDto, and now Nest will do the validation via the ValidationPipe for you.
If it didn't work with you with service try to use
getConnection().createQueryBuilder().select().from().where()
I used it in custom decorator to make a isUnique and it works well, but niot with injectable service.
public async validate(slug: string, args: ValidationArguments): Promise<boolean> { return (await getConnection().createQueryBuilder().select(PollsEntityAlias).from(PollsEntity).where('PollsEntity.slug =:slug',{slug}))) ? true : false; }
That’s so greeat! Thanks a lot, it’s working.
I’ve tried something like that, but can’t find the good way.
The deconstructed { slug }: SlugDto, so tricky & clever ! I’ve tried slug : SlugDto, but it couldn’t work, I was like «..hmmm… how to do that… »
Just something else : in the controller method, I was using (as in documentation) #Param('slug'), but with the slugDto, it can’t work. Instead, it must be just #Param().
Finally, my method :
#Post()
#ApiParam({ name: 'slug', type: String })
public async create(#Param() { slug }: SlugDto, #Body() createChoiceDto: CreateChoiceDto): Promise<Choice> {
return await this.choicesService.create(slug, createChoiceDto);
}
And the dto :
export class SlugDto {
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
#IsSlugMatchingAnyExistingPoll()
slug: string;
}
Personally, I wouldn't register this as a class-validator decorator, because these are beyond the scopes of Nestjs's dependency injection. Getting a grasp of a service/database connection in order to check the existence of a poll would be troublesome and messy from a validator constraint. Instead, I would suggest implementing this as a pipe.
If you want to only check if the poll exists, you could do something like:
#Injectable()
export class VerifyPollBySlugPipe implements PipeTransform {
constructor(#InjectRepository(Poll) private repository: Repository<Poll>) {}
transform(slug: string, metadata: ArgumentsMetadata): Promise<string> {
let found: Poll = await repository.findOne({
where: { slug }
});
if (!found) throw new NotFoundException(`No poll with slug ${slug} was found`);
return slug;
}
}
But, since you're already fetching the poll entry from the database, maybe you can give it a use in the service, so the same pipe can work to retrieve the entity and throw if not found. I answered a similar question here, you'd just need to add the throwing of the 404 to match your case.
Hope it helps, good luck!

Angular Universal with Firestore "get()" doesn't work but "valueChanges()" does

I have been trying to make my Angular application to an Angular Universal application which gets the data from Firebase Firestore.
I have been following the guide below:
https://fireship.io/lessons/angular-universal-firebase/
The initial compilation and things work all fine and even when getting the data from Firestore with the help of valueChanges() method does the trick.
However if I change valueChanges() to get() and adjust the required things on the DocumentSnapshot it seems that the universal doesn't wait for the data to be fetch and gets rendered.
TEMPLATE FILE
<div class="desc" *ngIf="(task | async) as ts">
{{ts.description}}
</div>
WORKING CODE
export class Task {
colRef: AngularFirestoreCollection;
constructor(private db: AngularFirestore) {
this.colRef = this.db.collection(this.COL_NAME);
}
ngOnInit() {
this.task = this.colRef.doc(id).valueChanges();
}
}
NOT WORKING CODE
export class Task {
colRef: AngularFirestoreCollection;
constructor(private db: AngularFirestore) {
this.colRef = this.db.collection(this.COL_NAME);
}
ngOnInit() {
this.task = this.colRef.doc(id).get().pipe(map(docSnapshot => docSnapshot.data()));
}
}
valuechanges() is a method inside the package angularFire and it returns an Observable while get() method is inside the firestore package and it returns a Promise. Therefore you cannot use pipe with get()
Angularfire:
https://github.com/angular/angularfire/blob/master/docs/firestore/documents.md#valuechanges
get():
https://firebase.google.com/docs/firestore/query-data/get-data#get_a_document

Make a request for Json data inside a constructor of an Angular 4 home module?

I'm a total newbie into Angular/Typescript web development.
I'm developing a website using ASP.NET Core 2.0 and Angular 4. It needs to fetch some data and present it at the homepage (that would be the home component of the Angular app). I've seen some examples and they suggest doing something like this:
import { Component, Inject } from '#angular/core';
import { Http } from '#angular/http';
#Component({
selector: 'quotes',
templateUrl: './quotes.component.html'
})
export class FetchDataComponent {
public quotes: Quote[];
constructor(http: Http, #Inject('BASE_URL') baseUrl: string) {
http.get(baseUrl + 'api/quotes/recent').subscribe(result => {
this.quotes = result.json() as Quote[];
}, error => console.error(error));
}
}
interface Quote {
text: string;
author: string;
timeStamp: Date;
}
That code works fine when the component is not the first one to be presented when the page is loaded. If I try to fetch data on the home component, the server freaks out and throws all kind of exceptions. First, it throws a TaskCancelledException, and further requests throw:
NodeInvocationException: Prerendering timed out after 30000ms because the boot function in 'ClientApp/dist/main-server' returned a promise that did not resolve or reject.
I'm assuming that I'm doing stuff very wrong, but I haven't seen any other way of doing what I want.
I tried moving the offending code (the http.get request) to a separate function, but now I don't know how am I supposed to call it when the component finished loading.
import { Component, Inject } from '#angular/core';
import { Http } from '#angular/http';
#Component({
selector: 'quotes',
templateUrl: './quotes.component.html'
})
export class FetchDataComponent {
public quotes: Quote[];
private ht: Http;
private url: string;
constructor(http: Http, #Inject('BASE_URL') baseUrl: string) {
this.ht = http;
this.url = baseUrl;
}
fetchQuotes(): void {
this.ht.get(baseUrl + 'api/quotes/recent').subscribe(result => {
this.quotes = result.json() as Quote[];
}, error => console.error(error));
}
}
interface Quote {
text: string;
author: string;
timeStamp: Date;
}
No http event has helped me. I can make everything work using the (click)="" directive, but obviously, I don't want the user to have to click something for the app to work as expected. No other directive seems to work either.
Below is the code I have on the html of the component:
<p class="warning" *ngIf="!quotes" (click)="fetchQuotes()">
<span class="glyphicon glyphicon-warning-sign"></span>
<em>There's nothing to show yet.</em>
</p>
<div *ngIf="quotes">
<ul class="quoteList" *ngFor="let quote of quotes">
<li>
{{ quote.text }}
<small>{{ quote.author }}, {{ quote.timeStamp }}</small>
</li>
</ul>
</div>
So to summarize, I need a way to fetch data for the component that Angular will show by default upon loading the page.
So to summarize, I need a way to fetch data for the component that
Angular will show by default upon loading the page.
The typical way to do this is to implement the Angular OnInit interface, which allows you to do initialization in the ngOnInit() callback method.
import { OnInit } from "#angular/core";
// ...
export class FetchDataComponent implements OnInit {
ngOnInit() {
// do initial data load here
}
}

mobx observable array flow errors

I'm getting a flow error flow: property replace (Property not found in Array) when I'm calling replace on my array. How do I tell flow it is a mobx observable array? I already made the change to my flowconfig [libs] to include mobx
/* #flow */
import { observable } from 'mobx'
export default class GiphyStore {
#observable images = []
async getImageList() {
try {
// make axios network request
const imgs = response.data.data.map(item => {
return { id: item.id, url: item.images.downsized.url }
})
this.images.replace(imgs) // getting error???
} catch (e) {}
}
}
According to the test file provided by mobx you need to;
Use IObservableArray<> type for your arrays
Define your observable without the decorators as flow does not support them yet.
It was a joint effort of finding the answer, thanks a lot.

VueJS - How to make models and collections?

I am attempting to learn VueJS and I'm finding it hard to understand how to make models and collections.
For example, I want to have a collection (or list) of Employees and each Employee is a model.
But I'm not sure how to accomplish this in VueJS
Many thanks
Vue was initially created to bind data to a template in a reactive way, therefore, there's no "controller" or "model" notion like you would have in a regular MVC.
Vue just needs plain javascript objects as data, so if some of your data needs to be mapped to a model, well it's not about Vue, it's about... Javascript.
Here is an example of implementation (in ES6) :
class UserModel {
constructor(data) {
this.data = data
}
name() {
return this.data.firstname + ' ' + this.data.lastname
}
// and so on, put other methods here
}
var app = new Vue({
el: '#app',
data: {
user: {}
},
created() {
// axios or anything else for your ajax, or a new fetch api
axios.get('/me')
.then(response => {
// of course the "this" here is the Vue instance because i used an es6 arrow function
this.user = new UserModel(response.data)
})
}
})
That's it.
As you can see, Vue doesn't have to do with the Model class I created, I just mapped my ajax data to a custom class, then mapped the result to the Vue instance.
As simple as that.

Resources