Undefined global variable but showing in view of Angular 2 - html

I am confused with my codes in Angular 2. In my ts file I have:
import { Test } from '../../../../models/test';
import { TestService } from '../../../../services/test.service';
import { Job} from '../../../../models/job';
import { JobService } from '../../../../services/job.service';
export class TestTakeMcComponent implements OnInit {
company_name: string;
test: Test;
job: Job;
constructor(
private testService: TestService,
private jobService: JobService
) { }
ngOnInit() {
this.getTest();
this.getJob(this.test.id);
}
getTest(){
this.testService.getById(40).subscribe(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
}
}
);
}
getJob(id: number){
this.jobService.getJobByTestId(id).subscribe();
}
}
And in my HTML file I have:
<h3 class="box-title">{{ test?.title }} </h3>
Surely, the data binding {{ test?.title }} is working and showing the data. But during the call of another function getJob(this.test.id) on my ts file, it says an undefined parameter.
How this became undefined when it is showing perfectly in the view? I wanted to use the data inside this.test variable to other functions but I cannot since it is undefined.
Please someone has the same issue with me and how did you fix this problem. Thank you.

This is because the console.log() is executed before this.test gets a value assigned. The execution is async, which means it scheduled to run later, while the execution of the sync code continues immediately.
{{ test?.title }} is undefined first but updated later, but the change happens too fast for a human too recognize.
If you move the console.log() you'll see the value
getTest(){
this.testService.getById(40).subscribe(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
console.log(this.test);
}
}
);
}
update
export class TestTakeMcComponent implements OnInit {
company_name: string;
test: Test;
job: Job;
constructor(
private testService: TestService,
private jobService: JobService
) { }
ngOnInit() {
this.getTest().subscribe(val =>
this.getJob(this.test.id));
}
getTest(){
// add `return` and replace `subscribe` by `map`
return this.testService.getById(40).map(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
}
}
);
}
getJob(id: number){
this.jobService.getJobByTestId(id).subscribe();
}
}

Replace you code with this :
import { Test } from '../../../../models/test';
import { TestService } from '../../../../services/test.service';
export class TestTakeMcComponent implements OnInit {
company_name: string;
test: Test;
constructor(
private testService: TestService
) { }
ngOnInit() {
this.getTest();
// console.log(this.test);
}
getTest(){
this.testService.getById(40).subscribe(
test => {
if(test.data.data.length != 0){
this.test = test.data.data[0];
console.log(this.test);
this.getJob();
}
}
);
}
getJob(){
this.jobService.getJobByTestId(this.test.id).subscribe();
}
}
You have just put the console.log() at wrong place.
As this.testService.getById(40).subscribe is async part , so
ngOnInit() {
this.getTest();
// at this time we'll not have this.test ;
// console.log(this.test);
}

Related

I can’t find an element from an array which is has stored an array from a service. In console output it’s always show undefined message

Here is my component code. In this code I have stored all data in a local array to find an item from this array. But when I try to get an element from this array it shows undefined.
//-------------------------------------------------------------
Component.ts
export class AccountsComponent implements OnInit
{
retVal = [];
constructor(
public service:AccountingService)
{
this.service.getAccounts().forEach(item=>{
this.retVal.push(item['chartofaccount']); // Locally stored the value to an array//
});
}
ngOnInit()
{
console.log(this.getAccountById(2));
}
getAccountById(id)
{
return this.retVal.find(x => x.id === id); // Return value showed undefined//
}
} //-------------------------------------------------------------
Service.ts
getAccounts():Observable<ChartOfAccount[]>
{
return this._htc.get<ChartOfAccount[]>(this.apiUrl+'chart-of-account', httpOptions)
.pipe(
tap(data => console.log("Data:", data)),
);
}
Try to call your service methods in new method in your component instead of constructor.
This approach should fix your problem.
Why?
Angular: function calling on constructor
https://www.angularjswiki.com/angular/what-is-the-difference-between-constructor-and-ngoninit-in-angular/
//-------------------------------------------------------------
Component.ts
export class AccountsComponent implements OnInit
{
retVal = [];
constructor(
public service:AccountingService)
{
});
}
ngOnInit()
{ this.getAccountsData();
console.log(this.getAccountById(2));
}
getAccountsData() {
this.service.getAccounts().forEach(item=>{
this.retVal.push(item['chartofaccount']); // Locally stored the value to an array//
});
}
getAccountById(id)
{
return this.retVal.find(x => x.id === id); // Return value showed undefined//
}
} //-------------------------------------------------------------

Problem withAngular when I want to add elements in array

I have an animals array. I want to put in another array just the field "espece" of each animal.
I push all the espece of animals in especeAnimalPresenteTmp and then I remove the duplicates and save the next array in especeAnimalPresente.
I have this angular code :
import { Component, OnInit } from '#angular/core';
import { AnimalService } from "./animal.service";
import { Animal } from "./animal";
#Component({
selector: 'app-animal',
templateUrl: './animal.component.html',
styleUrls: ['./animal.component.css']
})
export class AnimalComponent implements OnInit {
private animaux:Array<Animal>;
private especeAnimalPresente:Array<string>;
private especeAnimalPresenteTmp:Array<string>;
constructor(private animalService: AnimalService) { }
ngOnInit() {
this.recupAllAnimals();
}
recupAllAnimals(){
this.animalService.getAllAnimaux().subscribe(data => {
this.animaux = data;
this.recupEspecePresent();
})
}
recupEspecePresent(){
// if (this.animaux){
for (let animal of this.animaux) {
this.especeAnimalPresenteTmp.push(animal.espece);
}
this.especeAnimalPresente = this.removeDuplicates(this.especeAnimalPresenteTmp);
// }
}
removeDuplicates(array) {
let unique = {};
array.forEach(function(i) {
if(!unique[i]) {
unique[i] = true;
}
});
return Object.keys(unique);
}
}
But I have this error in my console :
ERROR TypeError: "this.especeAnimalPresenteTmp is undefined"
recupEspecePresent animal.component.ts:32
recupAllAnimals animal.component.ts:24
RxJS 11
Angular 8
Someone can help me please ?
You have to initialize the array, for example in the constructor:
constructor(private animalService: AnimalService) {
this.especeAnimalPresenteTmp = [];
}

Using service.ts variables on multiple components

I've set up next.service.ts with 3 variables (user, action, rest) and made setters(updateNext()) and getters (getUser, getAction, getRest). I've got to use the setter to change the variables in one component (stock-management component) and retrieved these variables in another component (inventory-record component) but I can't seem to retrieve them from another component (inventory-record-filled component).
I've tried returning a string ("TEST") in the getter and it worked, but when I tried returning a variable, it just returned nothing/empty string.
export class NextService {
private action: string;
private user: string;
private restraunt: string;
constructor() { }
updateNext(actions, users, restraunts) {
this.action = actions;
this.user = users;
this.restraunt = restraunts;
}
getAction(): string {
return this.action;
}
getUser(): string {
return this.user;
}
getRest(): string {
return this.restraunt;
}
export class InventoryRecordComponent implements OnInit {
name = '';
rest = '';
action = '';
constructor(private next: NextService) {
this.name = this.next.getUser();
this.action = this.next.getAction();
this.rest = this.next.getRest();
}
ngOnInit() {
document.getElementById('dne').style.display = 'none';
}
onSubmit(f: NgForm) {
const x = document.getElementById('dne');
if (!this.next.updateCode(this.code)) {
x.style.display = 'block';
f.resetForm();
} else {
this.next.updateCode(this.code);
location.replace('inventory-record/qty');
}
}
}
export class InventoryRecordFilledComponent implements OnInit {
name: string;
action: string;
rest: string;
constructor(private next: NextService) {
this.name = this.next.getUser();
this.action = this.next.getAction();
this.rest = this.next.getRest();
}
ngOnInit() {
}
}
Each component have its respective html files with {{ name }} {{ action }} {{ rest }}
If you need your component to behave as a Simpleton (where it contains the same values regardless of where in the application it is used) you must set its providedIn value to "root", like so:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class NextService {
// The rest of the code stays the same
}
The description for that can be found here: https://angular.io/guide/singleton-services#providing-a-singleton-service
If you don't do that, each component that imports NextService will have it's own instance of NextService, with its own isolated values. If you want the values of a service to be available everywhere that the service is used in, then you want the service to be a Simpleton, so you must follow the steps.
Following the steps above is not the only way to make your component a Simpleton, but as the link mentions, it is the preferred way to do that.
Hope that helps!

Pagination - How to wait till dataSource is ready/available

I have a pagination service and component which is working fine except that the dataSource is empty when loading the page first time. By second time, the dataSource is ready and I can display the dataTable and paginate.
Is there any fix to work around this issue, so that the function is invoked after the data is ready/loaded?
setTimeOut() would not be an option based on my tries.
list-user.service.ts:
let registeredUser:USers[] = [];
#Injectable()
export class ListUserService {
constructor(public loginService:LoginService){}
getUserDatasource(page:number, limit:number, sortBy?:string, sortType?:DatatableSortType): IPaginableUsers {
this.loginService.getUsersList().subscribe(
(res) => {
registeredUser = res.message
return registeredUser;
}, (err) => {})
}
...
...
}
list-users.component.ts:
export class ListUsersComponent implements AfterViewInit, OnDestroy {
...
...
constructor( private listUserService:ListUserService, private changeDetectorRef:ChangeDetectorRef) {
this.fetchUserDataSource();
}
ngOnInit(){}
ngAfterViewInit() {
if (this.datatable) {
Observable.from(this.datatable.selectionChange).takeUntil(this.unmount$).subscribe((e:IDatatableSelectionEvent) =>
this.currentSelection$.next(e.selectedValues)
);
Observable.from(this.datatable.sortChange).takeUntil(this.unmount$).subscribe((e:IDatatableSortEvent) =>
this.fetchUserDataSource(this.currentPagination.currentPage, this.currentPagination.itemsPerPage, e.sortBy, e.sortType)
);
Observable.from(this.pagination.paginationChange).takeUntil(this.unmount$).subscribe((e:IDatatablePaginationEvent) =>
this.fetchUserDataSource(e.page, e.itemsPerPage)
);
}
}
ngOnDestroy() {
this.unmount$.next();
this.unmount$.complete();
}
shuffleData() {
this.users$.next(shuffle(this.users$.getValue()));
this.currentSelection$.next([]);
this.changeDetectorRef.detectChanges();
}
private fetchUserDataSource(page:number = this.currentPagination.currentPage, limit:number = this.currentPagination.itemsPerPage, sortBy:string | undefined = this.currentSortBy, sortType:DatatableSortType = this.currentSortType) {
if (sortBy) {
this.currentSortBy = sortBy;
this.currentSortType = sortType;
}
const { users, pagination } = this.listUserService.getUserDatasource( page, limit, sortBy, sortType);
this.users$.next(users);
this.currentSelection$.next([]);
this.currentPagination = pagination;
}
}
you MUST subscribe in an ngOnInit to this.listUserService.getUserDataSource in your list-user-component
export class ListUsersComponent implements OnInit,...
{
page:number=1;
limit:number=5;
sortBy:string='';
....
ngOnInit() {
this.listUserService.getUserDataSource(page, limit, sortBy, sortType).subscribe((res)=>{
....here ...
}
}

How can I sanitize css properties to use in template given from a data service

I need to generate sanitized css property to use with my component template to set the background image of the div:
<div *ngFor="let Item of Items"
[style.background-image]="Item.imageStyle
(click)="gotoDetail(Item.iditems)">
</div>
using data obtained through a data service. The component is:
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { DomSanitizer } from '#angular/platform-browser';
import { OnInit } from '#angular/core';
import { Item } from '../models/Item';
import { CollectionDataService } from '../services/CollectionData.service';
#Component({
selector: 'mainpage',
templateUrl: 'app/mainpage/mainpage.component.html',
styleUrls: ['app/mainpage/mainpage.component.css']
})
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.collectionDataService.getItems().subscribe(
Items => this.Items = Items
);
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
constructor(
private router: Router,
private sanitizer: DomSanitizer,
private collectionDataService: CollectionDataService
) {
}
gotoDetail($iditems: number): void {
this.router.navigate(['/viewer', $iditems]);
}
}
But it doesn't work because the statement that generates the sanitized property
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
doesn't find the loaded data. The error that I'm seeing in the browser console is:
core.umd.js:3070 EXCEPTION: Uncaught (in promise): Error: Error in ./MainpageComponent class MainpageComponent_Host - inline template:0:0 caused by: Cannot read property 'map' of undefined
TypeError: Cannot read property 'map' of undefined
The data service is:
import { Injectable } from '#angular/core'
import { Http } from '#angular/http'
import { Item } from '../models/Item';
import { DomSanitizer } from '#angular/platform-browser';
#Injectable()
export class CollectionDataService {
constructor(
private http: Http,
private sanitizer: DomSanitizer
) { }
getItems() {
return this.http.get('app/mocksdata/items.json').map(
response => <Item[]>response.json().items
)
}
}
And the provided items.json:
{
"items": [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
}
If I set static data in the component, instead of using the data service, everything works:
export class MainpageComponent implements OnInit {
Items: Item[];
ngOnInit() {
this.Items = [{
"iditems": 1,
"imageStyle": ""
}, {
"iditems": 2,
"imageStyle": ""
}]
// Generates and sanitizes image links
this.Items.map(
(LItem) => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)")
)
}
How can I force the sanitizer statement to wait that the async data are fully loaded? Alternatively how can I generate sanitized properties directly in the service?
EDIT
The best answer comes from PatrickJane below:
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}
I also solved this problem working directly in the service method (credits), but it is more verbose:
return this.http.get('app/mocksdata/items.json')
.map( (responseData) => {
return responseData.json().items;
})
.map(
(iitems: Array<any>) => {
let result:Array<Item> = [];
if (iitems) {
iitems.forEach((iitem) => {
iitem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+iitem.iditems+".jpg)");
result.push(<Item>iitem);
});
}
return result;
}
)
The subscribe function is async so your map function called before the subscribe function run. So in this phase the array is undefined because you doesn't set any initial value.
The solution is to do this inside the subscribe function and to initialize the Items with empty array.
Items: Item[] = [];
ngOnInit() {
this.collectionDataService.getItems().subscribe(Items => {
this.Items = Items;
this.Items.map(LItem => LItem.imageStyle = this.sanitizer.bypassSecurityTrustStyle("url(template/images/"+LItem.iditems+".jpg)"))}
});
}