I am using a globals file to set key data needed for the application. Everything works until you use the browser refresh button. Once that is clicked, all the data I am loading from the globals.ts file comes back as undefined when the template is loaded. After the template is loaded, the promise populates the data. I need the promise data to populate before the ng-template binding.
Notice that I am trying to populate the global data by calling this.globals.init(token) from the app.component. Then in the helpful-links template I am trying to build a url based on data passed in from the parent component (myAccount.component). Oddly enough, data in the parent component also uses the globals file, and populates fine. Where did I go wrong?
-app.component
constructor(public globals: Globals){}
async ngOnInit() {
...
this.globals.init(token);
...
}
-Globals.ts
#Injectable()
export class Globals {
async init(token:string): Promise<any>{
if(token === undefined || token === '') {
token = this.oidcSecurityService.getIdToken();
}
const decoded = jwt_decode(token);
console.log(decoded, '<<<<<<<<<--------- decoded!', this.title)
this.email = decoded.email;
await this.get('getuserdata', {}).toPromise()
.then(async (res) => {
// THIS IS THE NEEDED DATA THAT COMES BACK AFTER THE BINDING. all except localstorage is undefined
localStorage.setItem('email', res['body'][0].email);
this.userRole = res['body'][0].title;
this.setUserId(res['body'][0].userid);
this.userid = res['body'][0].userid;
this.email = res['body'][0].email;
this.subscriberid = res['body'][0].fk_tk_subscriber_id;
this.isManagerRole = res['body'][0].title === 'HR Admin' ? true : false;
this.setTitle(res['body'][0].title);
this.profilePic = res['body'][0].profilephotourl;
});
}
-my7Account.component.html file
...
<app-tk-helpful-links [glob]='this.globals'></app-tk-helpful-links>
...
--helpful-links template
#Component({
selector: 'app-tk-helpful-links',
templateUrl: './tk-helpful-links.component.html',
styleUrls: ['./tk-helpful-links.component.scss']
})
export class HelpfulLinksComponent implements OnInit {
#Input() glob: Globals;
public res: {};
subscription: Subscription;
async getHelpfulLinks(): Promise<any> {
if(this.globals.userid === undefined) {
this.globals.init('');
}
return this.dataSvc.get(`gethelpfulLinks/${this.glob.userid}/${this.glob.subscriberid}`, {})
.subscribe(res => {
this.res = res['body'];
});
}
constructor(
private dataSvc: TkDataService,
public globals: Globals
) {
}
async ngOnInit() {
this.getHelpfulLinks();
}
}
Related
I have a few decorators that when called, I want to setMetadata to use it in my logging,
In my controller, I have these:
#Post("somePath")
#Permission("somePermission")
#UseGuards(JwtAuthGuard)
#HttpCode(200)
#Grafana(
"endpoint",
"functionalmap"
)
async getSubscriptionFromPwebFormFilter(
#Body(ValidationPipe) someDto: someDtoType
): Promise<ISuccessResponse> {
// some logic
}
In my decorators I want to set some data into the metadata to use in my logging inteceptor,
Grafana decorator:
export const Grafana = (functionalMap: string, endpoint: string) =>
applyDecorators(
SetMetadata("endpoint", endpoint),
SetMetadata("functionalMap", functionalMap)
);
AuthGuard decorator:
#Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
constructor(
private readonly reflector: Reflector,
private readonly someService: SomeService
) {
super();
}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const role = this.reflector.get<string>("permission", context.getHandler());
const request = context.switchToHttp().getRequest();
const { valueToLog } = request.body;
const jwtToken = request.headers.authorization;
console.log("check value exist", valueToLog);
SetMetadata("valueToLog", valueToLog);
}
Now, in my logging interceptor, I am getting all the values of the metadata this way:
#Injectable()
export default class LoggingInterceptor {
constructor(private readonly reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler) {
const executionStart = Date.now();
return next.handle().pipe(
tap((responseData) => {
const response = context.switchToHttp().getResponse<ServerResponse>();
const { statusCode } = response;
const valueToLog = this.reflector.get(
"valueToLog",
context.getHandler()
); // this is undefined
const endpoint = this.reflector.get("endpoint", context.getHandler()); // this have value
const functionalMap = this.reflector.get(
"functionalMap",
context.getHandler()
); // this have value
...
// some logic
})
);
}
}
In my case, the value of endpoint and functionalMap can be retrieved from the reflector, however, valueToLog is appearing as undefined,
Is setting of metadata not working for auth guard decorator?
I will be using the term "Decorator" in this explanation so I will first define it.
Decorators are functions that accept information about the decorated declaration.
Ex:
function ClassDecorator(target: typeof DecoratedClass) {
// do stuff
}
// it can then be used like this
#ClassDecorator
class DecoratedClass {}
when calling #SetMetadata(key, value) it returns a decorator. Functions like SetMetadata are referred to as Decorator Factories.
Decorators use the form #expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
In your example you called SetMetadata("valueToLog", valueToLog). This returns a decorator. Decorators must be called with information about the decorated declaration. To actually attach the metadata you can do something like this:
SetMetadata("valueToLog", valueToLog)(context.getHandler());
I'm new to angular and I wasn't sure how to implement synchronous api calls. I implemented async/await from a few articles I read but it still seems like the variables are undefined meaning the console is printing before even initializing the variable. I need it to be synchronous because code further down the cycle function depends on accurate variables.
I'm making a small program where people can upload their own images and it will be displayed on the stage component. I'm saving the images as a blob on a mysql database and retrieving them one at a time depending on the names provided in my nameList array variable
What am I doing wrong when calling the api via synchronous call?
stage.component.html
<div class="container">
<div class="slideshow" *ngIf="retrievedImage">
<ng-container>
<img [src]="retrievedImage"/>
<h1 *ngIf="!database_populated" style="color: red;">No Photo's to show. Please go back and upload</h1>
</ng-container>
</div>
</div>
stage.component.ts
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { interval } from 'rxjs';
import { ImagingService } from '../../services/imaging.service';
#Component({
selector: 'app-stage',
templateUrl: './stage.component.html',
styleUrls: ['./stage.component.css']
})
export class StageComponent implements OnInit {
constructor(private httpClient: HttpClient, private imageService: ImagingService) { }
retrieveResponse: any;
public namesList: any;
imageName: string = "eating.jpg";
base64Data: any;
retrievedImage: any = null;
currentImage = 0;
public database_populated: boolean = false;
totalImages: any;
ngOnInit(): void {
this.checkCount().then(count => {
if (count > 0 ) {
this.database_populated = true
console.log("database is populated. going to cycle")
this.cycle()
}
else {
this.database_populated = false;
}
}) }
cycle(){
console.log("entering cycle")
interval(10000).subscribe(x =>
{
// update how many images there are in the database
this.checkCount().then(data => {
this.totalImages = data
})
console.log(this.totalImages)
//update the list of image names found in the database
this.updateNamesList().then(nameList => {
this.namesList = nameList;
})
console.log(this.namesList)
if (this.currentImage == this.totalImages){
console.log("inside mod")
this.currentImage = this.currentImage % this.totalImages
}
else
{
console.log("printing pictures")
// display the Nth image in the list
this.imageName = this.namesList[this.currentImage]
// increment the image count in case there is another image added to the database
this.currentImage = this.currentImage + 1
this.getImage()
}
});
}
getImage() {
//Make a call to Sprinf Boot to get the Image Bytes.
this.httpClient.get('http://localhost:8080/halloween/get/' + this.imageName)
.subscribe(
res => {
this.retrieveResponse = res;
this.base64Data = this.retrieveResponse.picByte;
this.retrievedImage = 'data:image/jpeg;base64,' + this.base64Data;
}
);
}
async updateNamesList(){
return await this.imageService.updateNamesList()
}
async checkCount(){
return await this.imageService.checkCount()
}
}
imaging.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ImagingService {
constructor(private httpClient: HttpClient) { }
public updateNamesList() {
return this.httpClient.get('http://localhost:8080/halloween/allnames').toPromise();
}
public checkCount() {
return this.httpClient.get('http://localhost:8080/halloween/check').toPromise();
}
}
this is a snippet of the browser console errors and it shows the variables as undefined even though I place the promise prior to the console.log
Your code will not work with asynch. Here is the order of execution.
// command 1
this.checkCount().then(data => {
//command 3
this.totalImages = data
});
// command 2, totalImages will be undefined.
console.log(this.totalImages)
There is no guarantee about time at command 2, because we fetch data through network, so delay time may take few seconds.
You can await the result of checkCount to make sure we have data through rest api.:
this.totalImages = await this.checkCount();
Or you can do other things after rest api have an data.
this.checkCount().then(data => {
this.totalImages = data
doSomethingWithTotalImagesHere();
});
I have 2 sibling components and I am showing an API result if there is a successful response and an API Failure error message if there is an API error. If there is an API Failure in both components, I need to show only 1 API Failure error message. I have managed to do this and I used a service to define booleans and use them in the components. However, I am wondering if there is a better solution like using Input, Output, EventEmitter or Subject.
message.component.html
<div *ngIf="isTodosLoaded && !appService.isAPIError">{{ todos.title }}</div>
<div *ngIf="!isTodosLoaded && appService.isAPIError">{{ appService.APIErrorMessage }}</div>
message2.component.html
<div *ngIf="isTodos2Loaded && !appService.isAPI2Error">{{ todos2.title }}</div>
<div *ngIf="!isTodos2Loaded && appService.isAPI2Error && !appService.isAPIError">{{ appService.APIErrorMessage }}</div>
message.component.ts
export class MessageComponent implements OnInit {
isTodosLoaded = false;
todos;
constructor(private messageService: MessageService,
private appService: AppService) { }
ngOnInit() {
this.messageService.getData()
.subscribe(
(response) => {
this.todos = response;
this.isTodosLoaded = true;
},
(error) => {
this.appService.isAPIError = true;
}
)
}
}
message.component.ts
export class Message2Component implements OnInit {
isTodos2Loaded = false;
todos2;
constructor(private message2Service: Message2Service,
private appService: AppService) { }
ngOnInit() {
this.message2Service.getData()
.subscribe(
(response) => {
this.todos2 = response;
this.isTodos2Loaded = true;
},
(error) => {
this.appService.isAPI2Error = true;
}
)
}
}
app.service.ts
export class AppService {
public isAPIError = false;
public isAPI2Error = false;
public APIErrorMessage = "API Failure"
constructor() { }
}
Source code: https://stackblitz.com/edit/angular-pwwicc
There is multiple ways to achieve that:
fetch the data in the parent and send it to the child with Input.
fetch the data somewhere in the app and save it in a service (in a Subject or in a variable) and get the data in your child from the service.
use ngrx for shared state between component.
second example:
in the parent:
fetch(...).subscribe(result => this.myService.setResult(result))
in your service:
result = new BehaviorSubject(null)
setResult(result):void{
this.result.next(result);
}
And in your child component:
this.myService.result.subscribe(result => {...})
This is my component.ts where when it's loaded I get the data from the api, which I can see in the console.log, I do infact get my array of 10 objects (they come in groups of 10 on the api). I have the correct path in the API for the source code of the first image in the array of 10 which I typed to out the correct path for in normal http/javascript format of data.hits.hits[n]._source.images[n].urls.original. However when I try to put it in angular it can't read the data value as it is right now since it's out of scope, but I can't figure out how to word it in a better way.
import { Component, OnInit } from '#angular/core';
import { ConfigService } from '../../config.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-property-binding',
templateUrl: './property-binding.component.html',
styleUrls: ['./property-binding.component.css']
})
export class PropertyBindingComponent implements OnInit {
private isHidden : boolean;
public zeroImage : string;
private Photos : Observable<Object>;
constructor(private configService: ConfigService) { }
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => console.log(data));
}
toggle() : void {
this.isHidden = !this.isHidden;
if(this.isHidden){
//var zeroImg = document.createElement("img");
this.zeroImage.src = data.hits.hits[0]._source.images[0].urls.original;
}
}
}
Here is the Angular html page that should property bind the src with the variable that I want.
<p>
View Artworks
</p>
<button class="btn btn-info" (click)="toggle()">Show Artwork</button>
<div class="col-md-4" *ngIf="isHidden">
<img [src]="zeroImage">
</div>
Here is the service method that I have the method that makes the API call
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ConfigService {
private httpOptions = {
headers: new HttpHeaders({
'ApiKey': 'my_personal_key'
})
};
private configUrl = 'https://api.art.rmngp.fr/v1/works';
constructor(private http: HttpClient) { }
getConfig(){
let obs = this.http.get(this.configUrl, this.httpOptions)
console.log("Inside the getConfig method, before subscribe, doing API call" +
obs);
//might not need to subscribe here??
//obs.subscribe((response) => console.log(response))
return obs;
//return this.http.get(this.configUrl, this.httpOptions);
}
}
And slightly unrelated code, this is the normal http/javascript where I wrote the code Outside of Angular, which works perfectly fine.
function displayPhoto(){
fetch('https://api.art.rmngp.fr/v1/works/, {headers: {ApiKey: "my_personal_key"}})
.then(function(response){
return response.json();
})
.then(function(data){
document.getElementById("zeroImg").src = data.hits.hits[0]._source.images[0].urls.original;
Again, the API call in Angular works, I can see I am pulling the data successfully, I however can not set the image to the first image in the set of data and have been struggling with it. any help will help
You are not doing anything with the data when you subscribe
this.Photos.subscribe((data) => console.log(data));
You have not done anything with the data here.
zeroImg.src = data.hits.hits[0]._source.images[0].urls.original;
zeroImg is a string and makes no sense to set a src property on it and data is undefined at the point. The only place there is a data variable is in your subscription function but it is not available here.
The following will set the src of the image
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
Make the toggle function just toggle the isHidden flag and get rid of the rest.
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
}
toggle() : void {
this.isHidden = !this.isHidden;
}
On my web-app written in angular I am posting data to a Database and I am displaying this data in a table on the same html. Each data record has an ID. And every time I am adding new data, the ID is going to be increased. The first input field shows the actual ID, see the screenshot below:
In my ngOnInit-method I am initialising the id and I call the function fbGetData() in order to display the data.
But now I am facing one odd problem:
Everytime I starting the application the initial value which is displayed in the ID-field is NaN.
Obviously I cannot post any data to the database because the ID is not a number. So I have to switch to another page on my application and then switch back. After that the correct ID is displayed. I also tried to move my methods from the ngOnInit-method to the constructor but this didn't help.
Somehow I think that I need to implement the methods asynchronously, but I have no idea how to do this, since I am quite new to Angular/Typscript.
I hope you guys can help me with this problem or give me any hint or idea.
I appreciate your answers!
Here is my .ts Code:
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { DataService } from '../data.service';
import { Http } from '#angular/http';
import { rootRoute } from '#angular/router/src/router_module';
import { SearchNamePipe } from '../search-name.pipe';
import { LoginComponent } from '../login/login.component';
import {NavbarService} from '../navbar.service';
declare var firebase: any;
const d: Date = new Date();
#Component({
selector: 'app-business-function',
templateUrl: './business-function.component.html',
styleUrls: ['./business-function.component.css'],
encapsulation: ViewEncapsulation.None,
providers: [DataService, SearchNamePipe, LoginComponent]
})
export class BusinessFunctionComponent implements OnInit {
id;
name: String;
descr: String;
typ: String;
bprocess: String;
appsystem: String;
applications: String;
datum: String;
liste = [];
bprocessliste = [];
applicationliste = [];
appsystemliste = [];
isDesc: boolean = false;
column: String = 'Name';
direction: number;
loginName: String;
statusForm: Boolean = false;
private idlist = [];
constructor(
private dataService: DataService,
private router: Router,
private route: ActivatedRoute,
private searchName: SearchNamePipe,
private navbarService: NavbarService
) {
this.datum = Date().toString();
}
ngOnInit() {
this.navbarService.show();
firebase.database().ref().child('/AllID/').
on('child_added', (snapshot) => {
this.idlist.push(snapshot.val()
)})
this.id = this.idlist[0];
console.log("ID: "+this.id);
console.log("IDlist: "+this.idlist[0]);
this.id++;
console.log("ID: "+this.id);
this.fbGetData();
}
fbGetData() {
firebase.database().ref().child('/BFunctions/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
//firebase.database().ref('/BFunctions/').orderByKey().on('child_added', (snapshot) => {
// alter code ... neuer Code nimmt nur die Validen mit dem X Flag
this.liste.push(snapshot.val())
});
// firebase.database().ref().child('/ID/').on('child_added', (snapshot) => {
//Bprocess DB Zugriff
firebase.database().ref().child('/BProcess/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.bprocessliste.push(snapshot.val())
});
//Appsystem DB Zugriff
firebase.database().ref().child('/Appsystem/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.applicationliste.push(snapshot.val())
})
//Application DB Zugriff
firebase.database().ref().child('/Application/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.applicationliste.push(snapshot.val())
});
console.log(this.applicationliste);
}
You need to update the id inside your callback:
firebase.database().ref().child('/AllID/').on('child_added', (snapshot) => {
this.idlist.push(snapshot.val())
this.id = this.idlist[0];
console.log("ID: "+this.id);
console.log("IDlist: "+this.idlist[0]);
this.id++;
console.log("ID: "+this.id);
this.fbGetData();
})
Otherwise id retains it initial undefined value. This is because the call to firebase is asynchronous.
Here is what happens in your original code:
call to firebase API... wait your response
set id to this.idlist[0], which is empty (undefined)
...some time later, getting response from firebase
id does not get updated because the code in point 2. has already been executed.
Anything that you need to do when you get the result from an asynchronous call, must be executed inside the callback function.