I have a button that turns the pages. I need to remain in this component, change the ID in the address bar and thus I will change the information on the site. Data coming to me, look like this:
{
"users": [
{
"id": "3135",
"name": "Lulu"
},
{
"id": "8173",
"name": "Lala"
},
{
"id": "5783",
"name": "Lolo"
}
]
I am outputting detailed information about the user by getting an id. My navigation has looks like:
{ path: 'list', component: ListComponent },
{ path: '', redirectTo: '/list ', pathMatch: 'full' },
{ path: 'details/:id', component: DetailComponent }
I have a button, by pressing which the transition from '/details/Id' to 'details/nextId'. But this button does not work. This is a request for the next ID in the service. (I get the data from Database API)
private users: User[] = new Array<User>();
private locator = (u: User, id: number) => u.id === id;
getNextId(id: number): number {
let index = this.users.findIndex(u => this.locator(u, id));
if (index > -1) {
return this.users[this.users.length > index + 2
? index + 1 : 0].id;
} else {
return id || 0;
}}
HTML:
<button class="next-user" [routerLink]="['details', userService.getNextId(user.id)]" routerLinkActive="active">
Next User
</button>
DetailsListComponent:
private userService: UserService,
private activeRoute: ActivatedRoute,
private location: Location,
private router: Router) {
activeRoute.params.subscribe(params => {
this.detail = params['details'] === 'details';
let id = params['id'];
if (id !== null) {
Object.assign(this.user, userService.getNextId(id));
}
});
}
detail: boolean = true;
I searched the Internet and here the answer to this question, but found only suitable for php. Please, help me. What am I doing wrong? I just recently began to study angular 7 and do not always understand what to do.
I do not know how to explain it correctly, how to explain my problem. But I will try, I have a page that displays a list of my users. Clicking on the user opens a new page with more detailed information about it. In order not to go back to the page with a list of users and not to select the next user, a page with detailed information about the user assumes the presence of the Next button. When I click on this button, the array is iterated, it is determined by which ID the page is currently open and which ID is the next in this array, after which the page goes to the next ID. That is, the same page is opened, but the information on it is about another user who is following the list after the previous user.
But my button is currently not working and I do not understand why.
I didn't understand what issue you are facing exactly but here is an overview of how you can route to next User id by clicking on button.
// in Component.ts file
// import Router from #angular/router
import { Router } from '#angular/router';
constructor(private _router: Router){}
// to get the next user id
getNextId(id: number): number {
// write your logic to get the next id here
}}
//navigate to user id
getNextUser(id:number){
let nextId = this.getNextId(id); // holds next id
this._router.navigate([`/register-step2/${nextId}`]);
}
<!-- In Component.html -->
<div class="userDetails">
<p> {{user.name}} </p>
<p> {{user.age}} </p>
<p> {{user.occuption}} </p>
</div>
<button class="next-user" (click)="getNextUser(user.id)">
Next Movie
</button>
I assumed that for each userid service gets called and the result of the service is stored on user variable. I Hope it helps .
=== EDIT (After the question was clarified) ===
Now I understand your use case. Here is how I would go about it.
Few things to note so that the example is clear:
list route was renamed to users (makes more sense to me)
Brand new UsersComponent is created to handle all the users-related routes
DetailComponent -> UserDetailsComponent
ListComponent -> UsersListComponent
Those are just my coding conventions so that things are clear. You can leave your naming style.
In your root component, you will have your root <router-outlet>. And you will also have another <router-outlet> in UsersComponent.
In your router settings you would have:
{ path: 'users', component: UsersComponent, children: [
{ path: '', component: UsersListComponent }
{ path: ':id', component: UserDetailsComponent }
] },
{ path: '', redirectTo: '/users ', pathMatch: 'full' }
Notice, I added a new component UsersComponent which will be a parent for both UsersListComponent and UserDetailsComponent. It will hold a <router-outlet> that will hold either UsersListComponent or UserDetailsComponent depending on the child route that is active.
For /users it will render UsersListComponent.
For /users/:id it will render UserDetailsComponent.
So, UsersListComponent will have something like the following template:
<div *ngFor="let user of users" class="user-list-item" >
<p>user.name</p>
<button class="open-user-details"
[routerLink]="['/users', user.id]">
Navigate To Details
</button>
</div>
In UserDetailsComponent you will have something like the following:
<div class="userDetails">
<p> {{user.id}} </p>
<p> {{user.name}} </p>
<p> {{user.age}} </p>
<p> {{user.occuption}} </p>
</div>
<button class="next-user"
[routerLink]="['/users', getNextUserId()]"
routerLinkActive="active">
Next User
</button>
And in .ts file for UserDetailsComponent you would have:
user: User; // complete user details object. ID included
constructor(private userService: UserService,
private activeRoute: ActivatedRoute)
// gets the next user id based on the current user.id
public getNextUserId(): number {
return this.userService.getNextId(this.user.id)
}
public ngOnInit(): void {
this.activeRoute.params.pipe(switchMap((params) => {
return this.userService.getUserById(params.id);
})).subscribe(user => {
this.user = user;
});
}
I assume your UserService has separate methods:
getNextId(curId: number): number
getUserById(userId: number): Observable<User>
Related
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'm implementing a simple system to import JSON elements in Angular.
Everything works fine: I've used an interface, an observable and a directive. You can find the JSON here: http://jsonplaceholder.typicode.com/todos
Now, I want to use "completed", the boolean from JSON file, to display or not users when the page is loaded. There is a boolean "showUSer" and a method "displayUSer()" but I don't get it...
I cannot correctly retrieve this JSON data.
Any ideas ? :>
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
interface JSP {
"userId": string;
"id": string;
"title": string;
"completed": boolean
}
#Component({
selector: 'app-product',
template: `<div class="display" *ngFor="let todo of todos">
<div>User Id: {{todo.userId}}</div>
<div >id: {{todo.id}}</div>
<div *ngIf="showUser">Title: {{todo.title}}</div>
</div>`,
styles: ['.display {margin-top: 20px; margin-bottom: 20px;}']
})
export class ProductComponent implements OnInit {
title: string = "Products List";
todos: JSP[];
showUSer: boolean;
constructor(private http: HttpClient){
}
ngOnInit(){
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => this.todos = result);
}
displayUSer(): void {
this.showUSer = this.todos.completed;
}
}
Nitpicks: Your question says to display or not users but your code seems to be display or not the title. Also why do you capitalize the 'S' in 'USers'?
The problem is this function which seems to ignore your actual data layout:
displayUSer(): void {
this.showUSer = this.todos.completed;
}
todos is a property of your controller. This is an array from the api call you make and it doesn't contain a completed property, so this.todos.completed will always be false. I'm a little surprised that you don't get an error compiling your typescript.
It looks like you want this flag to be on a 'todo item' basis and not page-wide, so this.showUSer doesn't make sense. Also you don't seem to be calling displayUSer to set the value in any case.
Since you are looking at an individual todo item and the query is simple, why don't you just look at the flag?
<div *ngIf="todo.completed">Title: {{todo.title}}</div>
If you are wanting to set a page-wide flag based on some critieria, you can do that when you subscribe to the results. Here I'm assuming that you will set the showUSer flag if any of the todo items is marked as completed:
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => {
this.todos = result;
this.showUSers = result.reduce((previous, current) => previous || current.completed, false);
});
Your JSON hasn't any json.completed value, but json[_].completed.
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.
For my app, the ItemDetailComponent is where info of an item will be displayed. I have a service that retrieves all items using promise. I use ActivatedRoute to retrieve the item ID from the url path, then run the service, get all items, then find the item with the ID retrieved above, and assign it to selectedItem variable.
Here is item-detail.component.ts:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
console.log('Inside promise', this.selectedItem)
})
console.log('Outside promise', this.selectedItem)
}
}
And here is item-detail.component.html template so I could display my item, just an example:
<div>
<h1>{{title}}</h1>
<div *ngIf="selectedItem">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app returns nothing but the title unfortunately. I then added the two console.log() commands and found out that the one outside of the promise as well as the html template are rendered before the promise is fulfilled, and no selectedItem is available at that time. How could I force the app to execute them only after the promise is resolved in order to have the selectedItem in place for displayed?
EDIT: I added a new line in the html template to examine further:
<div>
<h1>{{title}}</h1>
<div><label>Item ID 1: </label>{{selectedItem.itemId}}</div>
<div *ngIf="selectedItem">
<div><label>Item ID 2: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app displays "Item ID 1:" label but with no actual id there. The console shows me an error saying that "Cannot read property 'itemId' of undefined", again confirming that the whole template is rendered before promise resolved and is not re-rendered after data is loaded. So weird.
You could create a Resolver for the route that fetches the desired data.
https://angular.io/api/router/Resolve
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
Add a boolean variable in to your class like
private dataAvailable:boolean=false;
and in the subscription to the promise,make this true when the data is available
then((selectedItem) => {
this.selectedItem = selectedItem;
this.dataAvailable=true;
console.log('Inside promise', this.selectedItem)
})
and in the template render when the data is available
<div>
<h1>{{title}}</h1>
<div *ngIf="dataAvailable">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
It should do the trick
Update
ngOnInit() seems to be just a event handler hook - returning anything won't affect anything it seems. Hence my old answer will not work.
There are other workarounds like using *ngIf or putting it in routes etc. but I wish there was something like resolvePromise(): Promise hook that would put a condition on resolution before rendering.
This is instead of developers putting the boilerplate in every component.
Old answer
Most likely that is because you are missing return statement in the second then.
then((selectedItem) => {
this.selectedItem = selectedItem
console.log():
return selectedItem;//
}
Is it possible that the ChangeDetection is set to OnPush somewhere up the component tree?
If that is the case, the template does not automatically rerender, because nothing triggers the ChangeDetection for this component.
Look out for a Component with the setting changeDetection: ChangeDetectionStrategy.OnPush
#Component({
selector: 'example',
template: `...`,
styles: [`...`],
changeDetection: ChangeDetectionStrategy.OnPush
})
Also you already have a valid solution by using a Resolver you could check if this helps:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService,
// the reference to the components changeDetector is needed.
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
// this triggers the changedetection and the template should be rerendered;
this.changeDetectorRef.detectChanges();
console.log('Inside promise', this.selectedItem)
});
console.log('Outside promise', this.selectedItem)
}
}
Here is a great article about Angulars ChangeDetection: https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
Im trying to build a method inside a service that checks whether a navigation button should be showed to the current user based on his permissions or not (this is just cosmetic "security" I know). Therefore this is the button placed inside the template
<button [routerLink]="['/some/where']"
*ngIf="AuthService.isAuthorized(['some', 'where'])">
Personen
</button>
The method AuthService.isAuthorized uses the provided array to run through all available routes and get the required permissions from the particular route's data object:
{
path: 'some',
component: SomeComponent,
data: {
permissions: [
"read:some",
"edit:some"
]
},
children: [
{
path: 'where',
component: SomeComponent,
data: {
permissions: [
"read:where"
]
}
},
]
}
so in this case the permissions ["read:some","edit:some","read:where"] are needed by the current signed in user so that the button would be displayed to him. Working so far!
But since the function is called inside the template it is called multiple times because of angular change detection. How could I change my code so that the function is called only once? Even better if it would only be called once after the authentication finished writing all permissions assigned to the authenticated user into AuthService.permissions
You can make AuthService.isAuthorized() method returns a promise:
#injectable()
export class AuthService {
...
isAuthorized(arr: string[]): Promise<boolean> {
return new Promise(resolve =>{
// your logic here
resolve(yourResult);
});
}
...
}
You can call this method on your ngOnInit of a component (Therefore it will be called once). You pass the return value to a new variable (e.g. isAuthorized) in the component and use this variable in the template instead.
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent implements OnInit {
isAuthorized: boolean;
constructor(private authService: AuthService) {}
ngOnInit() {
this.authService.isAuthorized(['some', 'where']).then(result => {
this.isAuthorized = result;
});
}
}
In the template you can just use isAuthorized variable.
<button [routerLink]="['/some/where']"
*ngIf="isAuthorized">
Personen
</button>
Edit:
If AuthService.isAuthorized() needed to be called only once but for more than one element, code like these may suits your need:
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent {
isObjectAuthorized = {} as {
isFirstAuthorized: boolean;
isSecondAuthorized: boolean;
};
constructor(private authService: AuthService) {}
checkForAuthorization(isElementAuthorized, arr: string[]) {
if (isElementAuthorized !== undefined) {
return;
}
this.authService.isAuthorized(arr).then(result => {
isElementAuthorized = result;
});
}
}
And in your template:
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isFirstAuthorized, ['some', 'where'])">
First
</button>
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isSecondAuthorized, ['some', 'where', 'else'])">
Second
</button>