Angular 15 - View not updating after BehaviorSubject emmits change - html

I have a service with a BehaviourSubject which I can subscribe to from any component:
// getter in service
public get myObs$(): Observable<any> {
return this._myBhvSub$.asObservable()
}
Then in the component I do
ngOnInit(): void {
this.myService.myObs$.subscribe(res => {
this.myVar = res
console.log("My Var", this.myVar)
})
}
concole.log() shows the data !! myVar is getting its value correctly !!
Finally HTML
<div>{{myVar.someKey}}</div>
This view just won't show the data 😣😣😣
I also tested hardcoding MyVar like this:
ngOnInit(): void {
this.myService.myObs$.subscribe(res => {
this.myVar = {someKey: "Hello World"}
console.log("My Var", this.myVar)
})
}
...and this works just fine πŸ˜’
What am I doing wrong? 😭😭😭

can you try this:
<div>{{(myService.myObs$ | async).someKey}}</div>
N.B : make myService public
if async pipe work it mean you have onPush detection in your component or parent component

Related

How can i show a spinner while waiting for data from a get call inside my ngOnInit ? (Angular 15)

i'm struggling on something that should be pretty easy.
I'm trying to render a spinner, whenever a get call is ongoing, so instead of displaying an empty screen, i can use the spinner.
I thought of using two separate div, controlled by two ngIf, related to the same bool flag. Of course if one is *ngIf="flag", the other one is *ngIf="!flag".
I edit the value, inside the 'subscribe' of the my get call, but unfortunately, the bool (although it changes), does not affect the html (probably because how angular works, and lifecycle of the components).
Do you know how can i do this ?
In my data service component i have a really simple http get to fill my variable 'products : Product[]', and it works.
In my component shop.ts i have
#Component({
selector: 'app-shop',
templateUrl: './shop.component.html',
styleUrls: ['./shop.component.css'],
})
export class ShopComponent {
constructor(public ds: DataService) {}
/* Variables */
products: Product[] = [];
isDataLoaded: boolean = false;
/* With this get call, we get all the products informations, and we save'em
into products */
ngOnInit() {
this.ds.getProducts().subscribe((resp) => {
this.products = resp as Product[];
this.isDataLoaded = true;
}
});
}
In the component html i just have
<div *ngIf="!isDataLoaded">
<mat-spinner></mat-spinner>
</div>
<div *ngIf="isDataLoaded">
Data is loaded
</div>
I do this all the time. Here is the approach I use.
Store the result in a subscription and set it equal to the request like so:
#Component({
selector: 'app-shop',
templateUrl: './shop.component.html',
styleUrls: ['./shop.component.css'],
})
export class ShopComponent {
constructor(public ds: DataService) {}
products: Product[] = [];
isDataLoaded$: Subscription;
ngOnInit() {
this.isDataLoaded$ = this.ds.getProducts().subscribe((resp) =>
this.products = resp as Product[];
);
}
}
Then in your template, check if the subscription exists and is not closed:
<mat-spinner *ngIf="isDataLoaded$ && !isDataLoaded$.closed"></mat-spinner>
<div *ngIf="isDataLoaded$ && isDataLoaded$.closed">
Data is loaded
</div>
Problems with your original approach
If that request fails, your isDataLoaded variable will never update since you don't have an error block. Also, once you set that variable to true, it stays true. What happens if the user fires that request again? You need to also reset it back to false before each request so the spinner shows up.
Here is an improved version of your original code, although I do not recommend going with this approach.
ngOnInit() {
this.isDataLoaded = false;
this.ds.getProducts().subscribe((resp) => {
this.products = resp;
this.isDataLoaded = true;
}, error => {
...
this.isDataLoaded = true;
});
}
Can you show how you implemented the getProduts method?
I tried to replicate your project, like this:
constructor(public ds: DataService) {}
/* Variables */
products: Product[] = [];
isDataLoaded: boolean = false;
/* With this get call, we get all the products informations, and we save'em
into products */
ngOnInit() {
this.ds.getProducts()
.subscribe((resp) => {
this.products = resp;
this.isDataLoaded = true;
});
}
And I implemented the Data Service like this:
constructor(private http: HttpClient) {}
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>('API');
}
And it works. Maybe it works for you too, but the data are loaded so fast that you don't see the spinner.
import { finalize } from 'rxjs';
...
this.ds.getProducts()
.pipe(finalize(() => this.isDataLoaded = true))
.subscribe((resp) => {
this.products = resp as Product[];
});

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//
}
} //-------------------------------------------------------------

Angular can't read value of 'undefined' - unable to read value of 'casestudy' in the setTitle() method?

This is my component:
export class CaseStudyDetailComponent implements OnInit {
casestudy: CaseStudy;
constructor ( private caseStudyService: CaseStudyService, private route: ActivatedRoute, public titleService: Title ) { }
ngOnInit() {
this.route.params.subscribe((params: { Handle: string }) => {
this.caseStudyService.getCaseStudy(params.Handle).subscribe(casestudy => this.casestudy = casestudy);
});
this.titleService.setTitle(this.casestudy.Handle);
}
}
This is the service it is calling:
getCaseStudy(Handle: string): Observable<CaseStudy> {
return this.http.get<CaseStudy>(`${environment.apiPath}/project/handle/${Handle}`);
}
I want to be able to access the value of 'casestudy' in the 'setTitle()' method. I might potentially just be misunderstanding expected behaviour or have my syntax wrong.
Let me know if more information is required.
Because your console.log gets excecuted before your subscribe can set the response in the caseStudy.
To fix this put the console.log method in the subscribe
this.caseStudyService.getCaseStudy().subscribe(caseStudy => {
... code
console.log(caseStudy);
});

Angular 4 Need to refresh page in order to show data

I'm running in the same problem. I try to display the sex of the user inside my navbar component. I call for my service to get me the user object and then I try to set my 'gender' for use in HTML. Problem is I need to refresh the page in order to display the gender.. Any help please? :)
export class NavbarComponent implements OnInit {
title = 'navbar';
userIsLoggedIn: boolean;
user: User;
currentUser: Parent;
gender: string;
constructor(private authenticationService: AuthenticationService, private router: Router, private parentService: ParentService) {
authenticationService.userIsloggedIn.subscribe(isLoggedIn => {
this.userIsLoggedIn = isLoggedIn;
this.user = authenticationService.getUser();
});
}
ngOnInit(): void {
this.user = this.authenticationService.getUser();
this.userIsLoggedIn = this.user != undefined;
this.getParentFromUserEmail(this.user.email);
this.getSex();
}
private getParentFromUserEmail(email: string) {
this.parentService.getByEmail(email).map(
(response) => this.currentUser = response).subscribe(data => {
this.gender = data.type;
});
}
getSex() {
return this.gender;
}
}
HTML CODE
<div class="sidebar-account-content">
<h3>{{user?.firstname}} {{user?.lastname}}</h3>
<p *ngIf="getSex()">Test</p>
<p *ngIf="gender === 'F'">Father</p>
<p *ngIf="gender === 'M'">Mother</p>
</div>
I suppose that your "getUser" is a asynchronous call, therefore you not have the data of the user when call it. I suppose you must make some like
ngOnInit(): void {
authenticationService.getUser().then((user)=>{
this.user = user;
this.userIsLoggedIn = this.user != undefined;
this.getParentFromUserEmail(this.user.email);
this.getSex();
}
});
}
Why would you want to create a two way binding on a method? You can just, in your template say
<p *ngIf="gender">Test</p>
Then you can just edit the gender in your component.ts file in order to change it on the template. No need for a getter.
I fixed the problem, I changed my return types from my service to promises. Also when our app launched we route instantly to login page since its a secure platform. The app component rendered the navbar which it shouldn't so everything from there was full of bugs. Thanks for the help, cheers.

Array undefined in Typescript but works in HTML

I have a component that populates an Object array in its ngInit() method from a service which I then use the contents of in my HTML template.
My problem is I can use this data fine in the HTML template but if I try to use this same Object array in my TypeScript file I will get an undefined error.
Below is a simplified code example of my problem:
#Component({
selector: 'booking',
template: `
<div *ngFor="let r of requestedBookings">
<label>Requested on {{r.created | date: 'd MMM H:mm'}}</label>
</div>
`
})
export default class BookingComponent {
requestedBookings: Object[];
constructor(private bookingService: BookingService) {
}
ngOnInit() {
this.getRequestLog();
// Cannot read property 'length' of undefined error
// console.log(this.requestedBookings.length);
}
private getRequestLog(): void {
this.bookingService.getRoomRequestBooking(1,1,1)
.subscribe(data => this.requestedBookings = (data as any))
.results, err => {
console.log(err);
}
}
Why is it in the above example I can use the requestedBookings array as expected in the HTML template but inside the TypeScript file I receive undefined errors?
IMHO the correct way should be something like:
ngOnInit() {
this.getRequestLog();
}
private getRequestLog(): void {
this.bookingService.getRoomRequestBooking(1,1,1)
.subscribe((data)=>{
this.requestedBookings = data;
console.log(this.requestedBookings.length);
})
.results, err => {
console.log(err);
}
}
As explained before, the call to getRoomRequestBooking is async, so you should not expect it will finish before calling the console.log. Instead, you should use the requestedBookings.length value in a place where you do know it will exist. Hope it helps!!
I fixed this issue by using this constructor from the subscribe method. the complete parameter event happens after successful completion.
subscribe(next?: (value: T) => void,
error?: (error: any) => void,
complete?: () => void): Subscription;
Code is as follows:
ngOnInit() {
this.getRequestLog();
}
private getRequestLog() {
this.bookingService.getRoomRequestBooking(this.date, this.level, this.room)
.subscribe(
data => this.requestedBookings = (data as any).results,
err => {
console.log(err);
},
() => console.log(this.requestedBookings.length));
}