In my angular 2 application there is a component containing an array of objects that is passing the chosen (clicked) one to it's direct child component. This does display the data more detailed. I'm using the "SimpleChanges" feature to watch in this child component if the object given changed to make another http request to get the related comments from a database.
If I try to build it with npm I get an error, saying :
app/displayEntry.component.ts(23,41): error TS2339: Property 'entry' does not exist on type 'SimpleChanges'
If I just comment this part out, start npm and finally put it in there again and save it, there is no Problem anymore ( no erro and it works ).
My question is, is there a way to work around this behavior and can this cause any trouble later I don't foresee or should I just ignore it? Thanks for your help
Parent component:
import { Component, OnInit } from '#angular/core';
import { entry } from './Objekte/entry';
import { entryService } from './entry.service'
#Component({
templateUrl: 'app/Html_Templates/displayLastEntrys.template.html'
})
export class displayLastEntrys implements OnInit{
public entrys : entry[];
private entryChoosen: boolean;
private ChoosenEntry : entry;
constructor ( private entryservice : entryService){
this.entryChoosen = false;
}
ngOnInit() : void {
this.getData();
}
getData() {
this.entryservice.getFirstEntrys().then(entrys => this.entrys = entrys);
}
entryClicked(ent: entry){
this.entryChoosen = true;
this.ChoosenEntry = ent;
}
leaveEntry () {
this.entryChoosen = false;
}
voted( upordown : boolean ) {
}
}
Child component:
import { Component, Input, Injectable, OnChanges , SimpleChanges, Output, EventEmitter} from '#angular/core';
import { entry} from './Objekte/entry';
import { entryService } from './entry.service';
import { comment } from './Objekte/comments';
#Component({
selector: 'display-entry',
templateUrl: 'app/Html_Templates/displayEntry.template.html'
})
export class displayComponent implements OnChanges{
#Input() public entry : entry;
public comments : comment[];
private changecounter : number;
constructor(private service : entryService) {
this.changecounter = 0;
}
ngOnChanges(changes : SimpleChanges){
this.service.getComments(changes.entry.currentValue.id)
.then(com => this.comments = com )
.catch();
this.entry.viewed++;
// To implement :: change database
}
votedUp () : void {
this.entry.votes ++;
// To implement :: change database
}
votedDown () : void {
this.entry.votes --;
// To implement :: change database
}
}
The accepted solution is suboptimal for TypeScript, as you're defeating the type system.
SimpleChanges does not have an entry property, so the compiler quite rightly balks. The solution is to treat the changes object as an array:
ngOnChanges(changes : SimpleChanges){
if (changes['entry']) {
this.service.getComments(changes['entry'].currentValue.id)
}
}
Then you can continue to strongly type the ngOnChanges method.
To make the compiler not complain just change your method definition for parameter one from SimpleChanges to any:
ngOnChanges(changes: any) {
//...
Maybe it's changed a lot now but this works these days
import {Component, Input, OnChanges, SimpleChanges} from '#angular/core';
import {ConfigModel} from './config.model'
#Component({
selector: 'selector',
templateUrl: './template.html',
styleUrls: ['./styles.scss']
})
export class BlaComponent implements OnChanges {
#Input() config: ConfigModel;
ngOnChanges(changes: SimpleChanges): void {
if (changes.config && changes.config.currentValue) {
let config = <ConfigModel>changes.config.currentValue;
// do more
}
}
}
I myself got the compile error because i wasn't using .currentValue after calling changes.config
If you are completely dependent on the IDE's auto-completion, make sure to actually use SimpleChanges instead of just SimpleChange. A very thing to be overlooked at.
Related
At first glance problem looks really easy, but unfortunately I have a problem with covering test scenario when nullable getter is null. Considering the sample below:
#Component({
selector: 'sample-form-test',
templateUrl: './sample-form-test.component.html'
})
export class SampleFormTestComponent implements ngOnInit {
form?: FormGroup;
get name(): FormControl | null {
return this.form?.get('name') as FormControl;
}
constructor(private formBuilder: FormBuilderService) {}
ngOnInit() {
this.form = this.formBuilder.build();
}
It is always underlined in a code coverage report that the question mark inside the getter above is not covered by the tests. Even if I try to cover it by checking directly whether is that getter null right after a component creation:
describe('create SampleFormTestComponent', () => {
let spectator = Spectator<SampleFormTestComponent>; // from #ngneat/Spectator
const createComponent = createComponentFactory({ // from #ngneat
component: SampleFormTestComponent,
providers: MockProvider(FormBuilderService, {
build: () => {
new FormGroup({name: new FormControl})
}
}
});
it('should create', () => {
spectator = createComponent();
expect(spectator).toBeTruthy();
expect(spectator.component.name).toBeNull();
});
}
It doesn't help at all and branch coverage is always lowered by that nullable getters. The worst scenario is when I have them multiple inside the class - in that scenario it is not possible to meet code coverage requirements. Do you know how can I easily solve that problem?
UPDATE
The easiest way is to initialize form in a constructor – it will allow to access the form properties (even if those are empty) without necessity to check if those values are null ot not.
#Component({
selector: 'sample-form-test',
templateUrl: './sample-form-test.component.html'
})
export class SampleFormTestComponent implements ngOnInit {
form: FormGroup;
get name(): FormControl {
return this.form.get('name') as FormControl;
}
constructor(private formBuilder: FormBuilderService) {
this.form = this.formBuilder.build();
}
Here is the updated Stackblitz solution
I have some Service classes as follows:
//Cat Service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository, getManager } from 'typeorm';
import { CatRepo } from '../tables/catrepo.entity';
import { CatInterface } from './cat.interface';
#Injectable()
export class CatService {
constructor(
#InjectRepository(CatRepo)
private catRepo: Repository<CatRepo>,
) {}
async customFindAll(offset:number, limit: number): Promise<CatRepo[]> {
const entityManager = getManager();
const catRows = await entityManager.query(
`
SELECT * FROM CATREPO
${offset ? ` OFFSET ${offset} ROWS ` : ''}
${limit ? `FETCH NEXT ${limit} ROWS ONLY` : ''}
`,
);
return catRows;
}
formResponse(cats: CatRepo[]): CatInterface[] {
const catsResults: CatInterface[] = [];
.
//form cat response etc.
.
//then return
return catsResults;
}
}
//Pet Service:
import { Injectable } from '#nestjs/common';
import { getManager } from 'typeorm';
import { PetInterface } from './pet.interface';
#Injectable()
export class PetService {
async customFindAll(offset:number, limit: number) {
const entityManager = getManager();
const petRows = await entityManager.query(
`
JOIN ON TABLES......
${offset ? ` OFFSET ${offset} ROWS ` : ''}
${limit ? `FETCH NEXT ${limit} ROWS ONLY` : ''}
`,
);
//returns list of objects
return petRows;
}
formResponse(pets): PetInteface[] {
const petsResults: PetInteface[] = [];
.
. //form pet response etc.
.
//then return
return petsResults;
}
}
I am running a cron BatchService that uses these two services subsequently saving the data into respective batch files.
I'm calling CatService and PetService from the BatchService as follows:
/Start the Batch job for Cats.
if(resource === "Cat") {
//Call Cat Service
result = await this.catService.findAllWithOffest(startFrom, fetchRows);
finalResult = this.catService.formResponse(result);
}
//Start the batch job for Pets.
if(resource === "Pet") {
//Call Pet Service
result = await this.petService.findAllWithOffest(startFrom, fetchRows);
finalResult = this.petService.formResponse(result);
}
However, instead of the above I want to use these Services dynamically.
In order to achieve the CatService and PetService now extends AbstractService...
export abstract class AbstractService {
public batchForResource(startFrom, fetchRows) {}
}
//The new CatService is as follows:
export class CatService extends AbstractService{
constructor(
#InjectRepository(CatRepo)
private catRepo: Repository<CatRepo>,
) {}
.
.
.
}
//the new PetService is:
export class PetService extends AbstractService{
constructor(
) {super()}
.
.
.
}
//the BatchService...
public getService(context: string) : AbstractService {
switch(context) {
case 'Cat': return new CatService();
case 'Pet': return new PetService();
default: throw new Error(`No service found for: "${context}"`);
}
}
However in the CatService I'm getting the a compilation error...(Expected 1 Argument but got 0). What should be the argument passed in the CatService.
Also, the larger question is if this can be achieved by using NestJS useValue/useFactory...If so how to do it?
You can probably use useFactory to dynamically retrieve your dependencies but there are some gotcha's.
You must make the lifecycle of your services transient, since NestJS dependencies are registered as singletons by default. If not, you would get the same first service injected each time, regardless of the context of subsequent calls.
Your context must come from another injected dependency - ExecutionContext, Request or something similarly dynamic, or something you register yourself.
Alternative
As an alternative, you can implement the "servicelocator/factory" pattern. You're already halfway there with your BatchService. Instead of your service creating instances of the CatService and PetService, you have it injected and just return the injected services depending on the context. Like so:
#Injectable()
export class BatchService {
constructor(
private readonly catService: CatService,
private readonly petService: PetService
)
public getService(context: string) : AbstractService {
switch(context) {
case 'Cat': return this.catService;
case 'Pet': return this.petService;
default: throw new Error(`No service found for: "${context}"`);
}
}
}
The alternative is more flexible than using useFactory, since your context is not limited to what is available in the DI container. On the negative side, it does expose some (usually unwanted) infrastructure details to the calling code, but that's the tradeoff you'll have to make.
i have a service that returns an API response of type json, in this json object i have a list of number values.i can output those values on my webpage, but i would like to store the values in an array first to do some calculations on. i have tried many ways without success. please guide me
API response screenshot in postman
http call service
getTriggerCount():Observable<Trigger>{
return this.http.get(this.triggersUrl).pipe(
flatMap(count => transformAndValidate(Trigger, count)))
component
#Component({
selector: 'app-triggers',
templateUrl: './triggers.component.html',
styleUrls: ['./triggers.component.css']
})
export class TriggersComponent implements OnInit {
#Input() trigger: Trigger;
constructor(private triggerService: DbApiService) { }
ngOnInit() {
this.getTriggerCount();
}
getTriggerCount(){
this.triggerService.getTriggerCount() .subscribe(trigger => this.trigger = trigger);
}
}
Trigger Class
import { IsNumber, IsNotEmpty, IsString } from 'class-validator';
export class Trigger {
#IsNotEmpty()
#IsNumber()
result: number[];
constructor() { }
}
The issue is with your service, it should be like the following
getTriggerCount():Observable<any>{
return this.http.get(this.triggersUrl).map(res => res.json());
}
Using the new HttpClient it should like like this:
getTriggerCount():Observable<any>{
return this.http.get<any>(this.triggersUrl);
}
To make use of this in the component. You also do not need the #Input() for trigger. Your Trigger class is also over complicated for what you are doing. See below
public trigger: any;
getTriggerCount(){
this.triggerService.getTriggerCount()
.subscribe(trigger => this.trigger = trigger);
}
This will then have the response on the trigger object. If you want to make use of the object, to say, add all the numbers together, you would do the following:
addArray() {
let sum = this.trigger.reduce((a, b) => +a + +b, 0);
}
The +a and +b is to convert the item to a number. This won't work if the item can't convert to a number.
This question already has answers here:
How do I return the response from an Observable/http/async call in angular?
(10 answers)
Closed 5 years ago.
I have the following component:
import { Component, Input, OnInit } from '#angular/core';
import { ActivatedRoute, Params } from '#angular/router';
import 'rxjs/add/operator/switchMap';
import { ArticleStore } from '../../state/ArticleStore';
import { Article } from '../../models/article';
#Component({
selector: 'app-article-detail',
templateUrl: './article-detail.component.html',
styleUrls: ['./article-detail.component.css']
})
export class ArticleDetailComponent implements OnInit {
private article: Article;
constructor( private route: ActivatedRoute, private articleStore: ArticleStore ) { }
ngOnInit(): void {
this.route
.queryParamMap
.map((paramMap => paramMap.get('id') || 'None'))
.switchMap((id: string) => this.articleStore.getArticle(id))
.subscribe((article: Article) => {
this.article = new Article(article);
console.log(this.article) // <--returns full-filled object
});
console.log(this.article) // <-- undefined object
}
}
Inside the subscribe function, I get the proper object (this.article) and is what I expect. If I move down to after the this.route, it doesn't work. Should be straight forward to get the value assigned.
The whole project is here => https://github.com/flamusdiu/micro-blog
Edit
Kinda similar to How do I return the response from an Observable/http/async call in angular2?
I understand the async nature of the calls (actually more calls now-a-days are async).
When you nav to article/:id, it fires off the getArticle(id) function from the ArticleStore.ts
public getArticle (id: string) {
return this.pouchdbService.getArticle(id)
.then((res) => {return res.docs[0] });
}
This runs just fine. It pulls from my service:
public getArticle(id: string): Promise<any> {
return this._pouchDb.find({
selector: {_id: id }
});
}
In your application routes you should have something like:
{ path: '/articles/:id', component: ArticleDetailComponent},
Then your router will be able to act on the provided article route.
Also consider using a Resolver for getting data for the component before it is initialized. good luck with the blog :D
I've worked through the Angular superhero tutorial. It all works.
If i close the cmd window running NPM, then re-open a CMD window and reissue the NPM START command I get two errors
src/app/DashBoard.component.ts(12,44) TS2304 : Cannot find name 'OnInit'.
src/app/hero-list.component.ts(16, 434) TS2304 : Cannot find name 'OnInit'.
I can resolve this by removing
Implements OnInit
from both these classes,
run NPM start
re-add them (simply CTL Z in the editor)
make some change , save.
The app recompiles and I am off and running.
I have 4 classes that implement this function. I have studied them and can not figure out what makes 2 fail...
I have read posts that reference TS2304, but this seems to be a generic Function/Variable/Symbol not found message ...
I don't know what to post. I'm happy to post any of the code.
Is this caused by errors in modules this depends on (hero.ts)?
Here is one class that is failing in this manner.
This is the hero-list.component.ts file
(at various points in the demo/online examples, this is also named Heroes.component..)
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { Hero } from './hero';
import { HeroService } from './hero.service';
#Component({
selector: 'hero-list',
templateUrl: './hero-list.component.html' ,
providers: [HeroService],
styleUrls: [ './hero-list.component.css']
})
export class HeroListComponent implements OnInit {
heroes : Hero[];
selectedHero: Hero;
constructor(
private router : Router ,
private heroService: HeroService
) { }
ngOnInit(): void {
this.getHeroes();
}
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
getHeroes(): void {
this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}
gotoDetail() {
this.router.navigate(['/detail', this.selectedHero.id]);
}
add(name: string): void {
name = name.trim();
if (!name) { return; }
this.heroService.create(name)
.then(hero => {
this.heroes.push(hero);
this.selectedHero = null;
});
}
delete(hero: Hero): void {
this.heroService
.delete(hero.id)
.then(() => {
this.heroes = this.heroes.filter(h => h !== hero);
if (this.selectedHero === hero) { this.selectedHero = null; }
});
}
}
You have to import OnInit.
import { Component, OnInit } from '#angular/core';
The tutorial fails to mention that you need to add the import of OnInit to TypeScript file app.component.ts:
import { Component, OnInit } from '#angular/core';