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.
Related
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 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
I've card component, Home Page and Explore page. I have show all the cards in Explore page and user have the ability to favorite a particular card and it'll display in the Homepage.
the problem I have right now is
the favorited cards only appear in Homepage when I refresh the browser, It didn't show anything when I navigate using toolbar.
When I filtered particular favorited cards then delete it, it still appear. for example, I typed "food" in search bar and it appear 5 card, I delete 1 and cancel my search then the delete card still appearing. It only gone after refreshing the browser again.
Can anybody help me fix this bugs?.
Card Component favorite and unfavorited function
toggleFavorite(): void {
if (!this.card.isFavorite) {
this.userService.addUserFavorite(this.card.type, this.card.guid, 0, 0).subscribe((res) => {
if (res) {
this.card.isFavorite = true;
this.removeFromList.emit(this.card.id);
this.refreshCardList.emit(true);
}
});
} else {
this.userService.removeFavorite(this.card.guid, this.card.type).subscribe((res) => {
if (res) {
this.card.isFavorite = false;
this.refreshCardList.emit(true);
this.removeFromList.emit(this.card.id);
}
});
}
Home Component, this is how I display user favorited card in Home page
<div *ngIf="favorites.length > 0">
<app-card-list [cards]="favorites"></app-card-list>
</div>
Home Component ts
favorites: List<Favorite>;
loading: boolean;
getUserFavs$: Subject<void> = new Subject();
searchInput: string;
user: User;
query: string;
inputCtrl: FormControl = new FormControl();
constructor(private userService: UserService, private cdr: ChangeDetectorRef) {}
ngOnInit(): void {
this.loading = true;
this.getUserFavs$
.pipe(
switchMap(() => {
return this.userService.getUser();
}),
)
.subscribe((user: User) => {
this.favorites = user.userFavorites;
this.user = JSON.parse(JSON.stringify(user));
this.loading = false;
this.cdr.detectChanges(); // treid this but still not working
});
this.getUserFavs$.next();
this.filterChanged();
this.cdr.detectChanges();
}
Use ChangeDetectorRef...
constructor(private change: ChangeDetectorRef){
}
After getting response from service and assigning to card variable
Use this.change.detectChanges()
I use mat-dialog to edit details of my profile page. I'm getting an ExpressionChangedAfterItHasBeenCheckedError when I click the 'Edit age' button and the dialog window pops up.
I decided to extract the styling of all edit dialogs into a single edit.component:
edit.component.html
<div class="navigation-control">
<mat-icon (click)="onCancelButtonClicked()"
class="close-button">close</mat-icon>
</div>
<div class="content-main">
<ng-content select=".content-main"></ng-content>
</div>
<div class="content-bot">
<button mat-raised-button
(click)="onCancelButtonClicked()">Cancel</button>
<button mat-raised-button
(click)="onActionButtonClicked()"
[lnDisableButton]="actionButtonDisabled">{{actionButtonValue}}</button>
</div>
edit.component.ts
#Component({ selector: 'ln-edit', ... })
export class EditComponent {
#Input() actionButtonValue: string;
#Input() actionButtonDisabled: boolean;
#Output() cancelButtonClicked = new EventEmitter<void>();
#Output() actionButtonClicked = new EventEmitter<void>();
onCancelButtonClicked() {
this.cancelButtonClicked.emit();
}
onActionButtonClicked() {
this.actionButtonClicked.emit();
}
}
To avoid the ExpressionChangedAfterItHasBeenCheckedError when trying to disable buttons and controls, I used this snippet. But that didn't solve this issue.
disable-button.directive.ts
#Directive({ selector: '[lnDisableButton]' })
export class DisableButtonDirective {
#Input('lnDisableButton') isDisabled = false;
#HostBinding('attr.disabled')
get disabled() { return this.isDisabled; }
}
The following is the contents of a mat-dialog window. This gets instantiated when I click the 'Edit age' button. When I remove the [actionButtonDisabled]="actionButtonDisabled", the error goes away, but obivously I need that line to make the functionality disable the button.
age-edit.component.html
<ln-edit [actionButtonValue]="actionButtonValue"
[actionButtonDisabled]="actionButtonDisabled"
(cancelButtonClicked)="onCancelButtonClicked()"
(actionButtonClicked)="onActionButtonClicked()">
<form [formGroup]="ageForm"
class="content-main">
<ln-datepicker formControlName="birthday"
[appearance]="'standard'"
[label]="'Birthday'"
class="form-field">
</ln-datepicker>
</form>
</ln-edit>
I handle the disabling/enabling the button in the 'ts' part of the mat-dialog popup.
age-edit.component.ts
#Component({ selector: 'ln-age-edit', ... })
export class AgeEditComponent implements OnInit, OnDestroy {
ageForm: FormGroup;
private initialFormValue: any;
actionButtonDisabled = true;
private unsubscribe = new Subject<void>();
constructor(
private editPhotoDialogRef: MatDialogRef<AgeEditComponent>,
private fb: FormBuilder,
#Inject(MAT_DIALOG_DATA) public dialogData: Date) { }
ngOnInit() {
this.initializeAgeForm();
this.loadDataToAgeForm(this.dialogData);
this.trackFormDistinct();
}
private initializeAgeForm(): void {
this.ageForm = this.fb.group({
birthday: null,
});
}
loadDataToAgeForm(birthday: Date | null): void {
if (!birthday) { return; }
this.ageForm.setValue({ birthday });
this.initialFormValue = this.ageForm.value;
}
get birthdayAC() { return this.ageForm.get('birthday') as AbstractControl; }
get actionButtonValue(): string {
return this.birthdayAC.value ? 'Update age' : 'Add age';
}
onCancelButtonClicked(): void {
this.editPhotoDialogRef.close();
}
onActionButtonClicked(): void {
this.editPhotoDialogRef.close({ ... });
}
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
ngOnDestroy() { ... }
}
I suspect this has something to do with content projection, but I'm not sure.
(...or perhaps with my custom 'ln-datepicker'?)
Any ideas?
Thanks.
From what I can tell, the problem resides in trackFormDistinct() method:
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
Looks like because of this.ageForm.valueChanges, will have different values in the 2 change detection cycles. I think this.ageForm.valueChanges emits due to <ln-datepicker>.
In a tree of form controls, if one node calls setValue, all its ancestors will have to be updated. I've written more about how Angular Forms work in this article.
I'm thinking of 2 alternatives:
skip the first emission of ageForm since it indicates the initialization of the form control tree, so this is irrelevant to the logic inside subscribe's callback.
this.ageForm.valueChanges.pipe(
skip(1),
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(/* .... */)
initialize actionButtonDisabled with false, since the error complains that it switched from true to false
actionButtonDisabled = false;
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!