Accessing Observable object in Angular - javascript

I am currently learning Angular through some tutorial and I came across a problem. I don't quite understand why is my console giving me error
ERROR in src/app/employee/employee.component.ts:17:24 - error TS2322: Type 'IEmployee' is not assignable to type 'any[]'.17 .subscribe(data => this.employees = data);
This is my code that I followed from tutorial:
export class EmployeeDetailComponent implements OnInit {
public employees = [];
constructor(private _employeeService : EmployeeService) { }
ngOnInit(): void {
this._employeeService.getEmployees()
.subscribe(data => this.employees = data);
console.log(this.employees);
}
}
Http request should return observable with type of IEmployees interface that i made, but it doesn't work unless I do this public employees:any = [].This did solve my problem, but i don't understand why, since on tutorial it works without it. My second issue is when i try to Console log my employees array after i get my data with http request, to see what type of data it is, but it seems to be empty(i tried this.employees[0].name also). Why cannot I access that array within ngOnInit() but in my html file i can do it with ngFor and access object properties.
This is IEmployees interface
export interface IEmployee {
id: number,
name: string,
age : number
}

When declaring your employees array,
do this
public employees: IEmployee = [] ;
instead of this
public employees = [];
This is because your service is retuning the data of type IEmployee while the variable you're is using is not of that type

You seem to be unsure whether you're going to receive a single Employee object or an array of Employees.
You have declared the getEmployees method in your Employee service to return an Observable<Employee>.
If your API returns an array of Employees, change the getEmployees method such that it returns an Observable<Employee[]>.
If your API returns a single Employee object, change the arrow function to the following:
data => this.employees = [data].
To additionally log the output to the console, change the arrow function to:
data => { console.log(data); // set this.employees;}

The this.employees[0] is undefined in ngOnInit because the asynchronous request finishes later, than the ngOnInit finishes. So the value of this.employees will be assigned after the ngOnInit is done. The ngFor is also able to display the values of the property after the asynchronous response arrived.

Related

Best way to display a list within a json object via Angular

im trying to display the objects within a list within a json object via angular, but i can't really get it to work.
The json is delivered via a service as a resolved Promise:
getJson(): Promise<any> {
return Promise.resolve(this.testdata);
}
Thus function returns an [object Promise]
The testdata json string is of structure:
{"errorCode":"ok",
"objList":[{},{},etc.]
}
What i want is to display a list (for example with ngFor) and have access to the properties of the objects within the objList.
My initial idea was to just iterate over the object list with ngFor, to do this i created the following class
export class ExampleComponent implements OnInit {
json : {"objList": []}
constructor(
private router: Router,
public app: AppService,
public pService: pService
) {
}
ngOnInit(){
this.json = this.pService.getJson()
}
}
This does not work however, as im getting the error that property objList is missing in type Promise<any>.
I also tried ngFor with KeyValue pipe, however it also didnt work properly.
I'd appreciate any help.
Can you please try this code snipped
ngOnInit(){
this.pService.getJson().
then(response => {
this.json = response;
})
}
the issue is that this.pService.getJson() returns a Promise reference. To learn more about Promise click here
So here is the fix you need to apply:
ngOnInit() {
// you will need to implement the then call of the promise
this.pService.getJson()
.then((result) => {
this.json = result;
// then call the change detection, I will explain this below
return this.cd.detectChanges();
});
}
Additionally this may cause changeDetector issues for you so you may want
to inject the ChangeDetectorRef in the component constructor
constructor(
private router: Router,
public app: AppService,
public pService: Service,
public cd: ChangeDetectorRef,
) {
}
This should sort you out, but i'd further advise reading more about promises
and considering using Observable instead of Promise, check this link on Observable

Why isn't my component object getting updated itself when anything in my globally shared service updates? Angular

I have this service:
export class RecipeService{
selectedRecipe: Recipe = 'xyz';
}
I have this component using this service:
export class RecipesComponent implements OnInit {
selectedRecipe: Recipe;
constructor(private recipeService: RecipeService) { }
ngOnInit(): void {
this.selectedRecipe = this.recipeService.selectedRecipe;
}
}
The service is defined in app.module.ts for injection, which means all components get the same instance.
My question is, whenever I update the selectedRecipe variable in one of my components, it doesn't get updated back in other components although it is referenced and hence I expect a change immediately.
What am I doing wrong?
It doesn't get updated because the new value is not "sent" to the already initiated angular components.
Instead you should use observables.
for example:
/* service */
private recipe = "xyz";
public recipeSubject: BehaviorSubject<string> = new BehaviorSubject(this.recipe);
// when changing the recipe
recipeSubject.next(this.recipe);
/* component */
this.service.recipeSubject.subscribe(res => this.recipe = res);
I googled and found out in one of the posts that its because of my object. The object (Recipe) in my service contains a primitive type, i.e string. If your object contains a primitive type, it isn't passed as a reference, hence a change in service object won't be reflected in component because they are now different.
Although I must clear that in case of array it worked perfectly fine even when my array contained objects which had primitive types. Changes were still reflected.

Initializing properties inside ngOnInit scope

I'm using ngOnInit in order to get a response from a Loopback API call.
The property I initialize inside this scope can't be used outside the subscribe scope. I know this is a simple answer but I'm just not seeing it
private siteData: Kiosk;
constructor(private dataService: DataService){}
ngOnInit(){
this.dataService.getDataByID(this.dataService.getSiteID())
.subscribe((data:Site) => {
this.siteData = data;
console.log(this.siteData); // This outputs correctly
});
console.log(this.siteData); // This is undefined
}
site: any[] = [{
title: this.siteData.title, // this is undefined and throws error
}];
I expect output to not be undefined and be the data that is in the response
This is because observables are asynchronous. sideData isn't defined until after the assignment is made in the subscribe call – it's similar to a promise.
If you try to access the value set by a promise before a .then is called, then it will also be undefined. The way around this would be to use the tap operator to set it in a pipe, and then operate on it in other ways in the subscribe. Alternatively, you could use something like ngrx that will automatically update the to latest value.
I also want to point you towards developing a better understanding of the lifecycle hooks in Angular. You are setting your class property like this:
site: any[] = [{
title: this.siteData.title, // this is undefined and throws error
}];
This means, you are accessing this.sideData, which is only set in the ngOnInit hook that actually runs after the component has been initialised (constructed). Furthermore, you are only subscribing to the Observable in the lifecycle hook (OnInit), i.e. you are not actively assigning the result just yet, only after the observable emits a result. Your observable does not have to be asynchronous, but in your case most likely is.
Short story: You are getting errors, because you are trying to access a property of an object this.siteData, that is at that point of time undefined.
console.log(this.siteData); // This is undefined
because this doesn't wait for this.dataService.getDataByID to finish use the one inside if you don't want it to be undefined
You will need to initialize the variables inside the subscribe method with the values which you are doing already.
Now You can access them in the HTML once the request is completed. Make sure to add a check in the HTML i.e if siteData using *ngIf & then use those variables normally
fill your fields only when you get data like this:
private siteData: Kiosk;
site: any[];
constructor(private dataService: DataService){}
ngOnInit(){
this.dataService.getDataByID(this.dataService.getSiteID())
.subscribe((data:Site) => {
this.siteData = data;
site = [{title: data.title}];
});
}

Angular 4 Component variable of type Item initialized in subscribe response does not have methods defined in the Item class

I'm working on an Angular project for a new internal application. While I have some experience in AngularJS I am a near complete novice when it comes to Angular 2+. An issue I've run into that I can't make sense of involves the setting of some component class variables inside of a subscribe callback in the ngOnInit block. I will be a bit abstract with my details but I hope it provides enough context to convey the issue.
I have an item type defined as such:
export class Item {
name: String;
checked: boolean = false;
// this is a simplification of the actual type and method, I'm aware this seems unnecessary
toggle(set: boolean = false): void {
this.checked = !this.checked || set;
}
}
In my component I a declared array of items using a subscribe callback to a restful service.
import ...
#Component({
templateUrl: './item.component.html',
styleUrls: [ './item.component.less',
providers: [ ItemService ]
})
export class ItemComponent implements OnInit {
items: Item[];
allChecked: boolean = false;
constructor(private itemService: ItemService) { }
ngOnInit() {
this.itemService.getItems().subscribe(
(response) => {
this.items = items;
}
);
}
checkAll(): void {
this.allChecked = !this.allChecked;
for(let item of this.items) {
item.toggle(allChecked);
}
}
In the html (as well as in the "checkAll()" function of the component above) I reference the method defined on the item type like so:
<input ... (change)="checkAll()" />
...
<tr *ngFor="let item of items; index as i">
<td>
<input ... (change)="item[i].toggle()" />
</td>
</tr>
...
I previously had the array of items being mocked inside the component ngOnInit block. When this was the case these method calls executed fine. It is only after replacing the mock data with the actual restful service call that I am having issues reaching the toggle() method. I receive an error stating that the method I'm attempting to call on my item type is undefined.
I understand that scoping is a bit wonky in javascript/typescript but since I'm using the syntactic sugar arrow format for the subscribe callback the "this" should reference the correct context, yes? Additionally, since I was at a loss I also tried using the that = this; outside the service call and then referencing "that" inside the callback to make sure I was setting the items property on the outer class context. Either way, it seems like as soon as I exit the context of the subscribe callback any notion of items comes back as undefined. Why is this? Am I missing some key point of Angular 2+?
Edit: I realize I was a bit too deep in the hole and was mistaking my IDEs lack of proper mapping (showing component data as undefined when debugging even if it wasn't) when in reality the data was there as a json without any method definitions. I've added more code to this post and altered the text/title to reflect this.
You're getting a JSON response from your service call, not instances of your
Item class. As plain data objects, they do not have ItemImpl's method definitions. So you need to map each object to a class instance.
interface Item {
id: string;
name: string;
}
class ItemImpl implements Item {
id: string;
name: string;
constructor(item: Item) {
this.id = item.id;
this.name = item.name;
}
someMethod() {
// ...
}
}
Then map the result from the HTTP call:
return this.http.get<Item[]>(this.myItemsUrl)
.pipe(
map(items => items.map(i => new ItemImpl(i)))
);
In my opinion, it would be better style to put the logic (someMethod()) into a service instead of the Item data class.

Why is this change emitted using Rxjs

I'm a little bit confused as why the following snipped works as expected.
The idea of this service is to have a list of strings where if you add a string, it is removed 5 seconds later. Rxjs is used here:
#Injectable()
export class ErrorService {
private errors: Array<string> = [];
private emitErrorsChanged = new Subject<any>();
public emitErrorsChanged$ = this.emitErrorsChanged.asObservable();
constructor() {
this.emitErrorsChanged$.delay(5000).subscribe(
() => {
if (this.errors.length > 0) {
this.errors.shift();
}
}
);
}
public emitErrorChange(error: string) {
this.errors.push(`${error}`);
this.emitErrorsChanged.next(this.errors);
}
}
An error component is subscribed to this service errorService.emitErrorsChanged$.subscribe(...) and shows the strings in a list. Other components/services add strings by this.errorService.emitErrorChange(error.message).
My question is: why are the removed errors (5s) emitted to the error component? The errors are just removed from the list this.errors.shift(); but the change is not emitted by this.emitErrorsChanged.next(this.errors);
The behavior occurs because you are passing reference to your object (list in this case). The changes made by this.errors.shift(); are not emitted, but I guess you can see current state of this.errors thanks to Angular's change detection. I have prepared a demo (click) so you can see that the object reference is passed in your case - what means that the list in subscription is the exactly same array list. To prevent it you can pass a copy of your list, e.g. using spread operator like in this example:
this.emitErrorsChanged.next([...this.errors]);

Categories

Resources