Angular: How to kinda refresh the ngOnInit method - javascript

I have a sidebar with different redirects of specific products categories, when these buttons are clicked it redirects to a component that gets the URL params and makes a consult to service and retrieves the data of that specific category, the thing is, when a click it the first time, it works, but the second time it does not, it only changes the URL but does not refresh the data
sidebar.component.html
<div class="list-group">
<a [routerLink]="['products/category']" [queryParams]="{name:category.name}" class="list-group-item"
*ngFor="let category of categories">{{category.name}}</a>
</div>
And the component that makes the magic
export class ViewAllProductsByCategoryComponent implements OnInit {
searchCategory: any;
products: Product;
constructor(private activatedRoute: ActivatedRoute, private productsService: ProductsService) {
}
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(res => {
this.searchCategory = res.name;
});
this.productsService.searchCategoryProducts(this.searchCategory).subscribe(res => {
this.products = res;
console.log(this.products);
});
}
}
So, how do I refresh the data?

Angular by default doesn't re-initialize an already loaded component.
But there is a way to bypass that feature:
let newLocation = `/pathName/5110`;
// override default re use strategy
this.router
.routeReuseStrategy
.shouldReuseRoute = function () {
return false;
};
this.router
.navigateByUrl(newLocation)
.then(
(worked) => {
// Works only because we hooked
// routeReuseStrategy.shouldReuseRoute
// and explicitly told it don't reuse
// route which forces a reload.
// Otherwise; the url will change but new
// data will not display!
},
(error) => {
debugger;
}
);
Just set the .shouldReuseRoute function to return false, that way the component will reload.
Here's more detail on that topic.
https://dev.to/jwp/angular-s-naviation-challenges-20i2
You can also configure the router to reuse the route.

I've modified a bit john's answer, this is how I fixed it
export class ViewAllProductsByCategoryComponent implements OnInit, OnDestroy {
searchCategory: any;
products: Product;
mySubscription: any;
constructor(private activatedRoute: ActivatedRoute,
private productsService: ProductsService,
private router: Router,
) {
this.router.routeReuseStrategy.shouldReuseRoute = () => {
return false;
};
this.mySubscription = this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.router.navigated = false;
}
});
}
ngOnInit(): void {
this.activatedRoute.queryParams.subscribe(res => {
this.searchCategory = res.name;
console.log(this.searchCategory);
});
this.productsService.searchCategoryProducts(this.searchCategory).subscribe(res => {
this.products = res;
console.log(this.products);
});
}
ngOnDestroy(): void {
if (this.mySubscription) {
this.mySubscription.unsubscribe();
}
}
}

Related

Property change caused by route change won't update view - *ngIf

So I have a kind of custom select bar with products-header__select expanding the list on click. To do so I created the property expanded which is supposed to describe its current state. With *ngIf I either display it or not.
It works fine clicking the products-header__select. But a click on one of the expanded list's items changes the route, the path and some other element changes, but the products-header__select remains visible.
All good, but I want to collapse the list on route change - my approach was to listen to router events and then run expanded = false when the navigation has ended. - But somehow the view won't update and the list remains expanded, even though running console.log(this.expanded) inside of the router event returns false. Why won't it update then?
View:
<div class="products-header__select" (click)="expanded = !expanded">
<ul>
<li class="basic-text__small custom-select">{{mobileCategories ? (mobileCategories[0].name | transformAllProducts) : ''}}</li>
<div class="select-options" *ngIf="expanded">
<li class="basic-text__small" *ngFor="let category of mobileCategories.slice(1, mobileCategories.length); let i = index" routerLink="/products/{{category.name.toLowerCase()}}">
{{category?.name | transformAllProducts}}
</li>
</div>
</ul>
</div>
import {Component, Input, OnInit} from '#angular/core';
import {NavigationEnd, Router, RouterEvent} from '#angular/router';
#Component({
selector: 'app-products-header',
templateUrl: './products-header.component.html',
styleUrls: ['./products-header.component.scss']
})
export class ProductsHeaderComponent implements OnInit {
expanded = false;
url: string;
$categories;
#Input() set categories(value) {
if (value) {
this.$categories = value;
this.createArrayForMobile();
this.getActiveRoute();
}
}
mobileCategories: any[];
constructor(private router: Router) {
router.events.subscribe((event: RouterEvent) => {
if (event instanceof NavigationEnd) {
this.expanded = false;
this.url = event.url;
this.getActiveRoute();
}
});
}
ngOnInit(): void {
}
getActiveRoute() {
if (!this.mobileCategories) { return; }
const decodedUrl = decodeURI(this.url);
const index = this.mobileCategories.findIndex(item => decodedUrl.includes(item.name.toLowerCase()));
const obj = this.mobileCategories[index];
this.mobileCategories.splice(index, 1);
this.mobileCategories.unshift(obj);
}
createArrayForMobile() {
this.mobileCategories = [...this.$categories, {name: 'all'}];
}
}
That's how I use it:
<app-products-header [categories]="categories"></app-products-header>
Don't know the answer but things I would try would be:
Try subscribing to the router events in ngOnInit() {} rather than the constructor.
Try specifically calling change detection.
constructor(private router: Router, private cdr: ChangeDetectorRef) {
router.events.subscribe((event: RouterEvent) => {
if (event instanceof NavigationEnd) {
this.expanded = false;
this.url = event.url;
this.getActiveRoute();
this.cdr.detectChanges();
}
});}
Had to wrap the router event's code with a timeout:
constructor(private router: Router) {
router.events.subscribe((event: RouterEvent) => {
if (event instanceof NavigationEnd) {
setTimeout(() => {
this.expanded = false;
this.url = event.url;
this.getActiveRoute();
});
}
});
}

Method some() doesn't work as expected in Angular 8

I have an Angular app and I want to add follow/unfollow functionality for users. I'm trying to add isFollowed flag, so I will be able to know if user is followed or no, and depending on that I will show 2 different buttons: Follow and Unfollow. I'm using some() method for this purposes but it doesn't work. It shows me that isFollowed flag is undefined although it should show true or false. I don't understand where the problem is, here is my HTML relevant part:
<button *ngIf="!isFollowing; else unfollowBtn" class="btn" id="btn-follow" (click)="follow(id)">Follow </button>
<ng-template #unfollowBtn><button class="btn" id="btn-follow" (click)="unFollow(id)">Unfollow</button></ng-template>
TS component relevant part:
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from "#angular/router";
import { AuthenticationService } from '#services/auth.service';
import { FollowersService } from '#services/followers.service';
#Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
user;
id;
followers;
isFollowing: boolean;
constructor(
private authenticationService: AuthenticationService,
private followersService: FollowersService,
private router: Router,
private route: ActivatedRoute,
) { }
ngOnInit() {
this.id = this.route.snapshot.paramMap.get("id");
this.authenticationService.getSpecUser(this.id).subscribe(
(info => {
this.user = info;
})
);
this.followersService.getFollowing().subscribe(
data => {
this.followers = data;
this.isFollowing = this.followers.some(d => d.id == this.user.id);
}
);
}
follow(id) {
console.log('follow btn');
this.followersService.follow(id).subscribe(
(data => console.log(data))
)
this.isFollowing = true;
}
unFollow(id) {
console.log('unFollow btn');
this.followersService.unFollow(id).subscribe(
(data => console.log(data))
)
this.isFollowing = false;
}
}
Any help would be appreciated.
If you want it called everytime and to make sure this.user is populated. Then you could use a forkJoin
forkJoin(
this.authenticationService.getSpecUser(this.id),
this.followersService.getFollowing()
).pipe(
map(([info, data]) => {
// forkJoin returns an array of values, here we map those values to the objects
this.user = info;
this.followers = data;
this.isFollowing = this.followers.some(d => d.id == this.user.id);
})
);
Not tested this because I didn't have time. If you make a StackBlitz we could see it in action and try from there.
Hope this helps.

How to publish event from a component and receive it from another component in nativescript

How can I publish custom event from a component and receive it from another component in nativescript.
something like:
ComponentOne.ts
this.event.publish('someEvent', {name: 'a name'})
ComponentTwo.ts
this.event.subscribe('someEvent', (data) => {
const name = data.name;
})
You can use subject for this case
import { Injectable } from "#angular/core";
import { Subject } from "rxjs";
#Injectable()
export class MessageService {
private subject = new Subject<any>();
constructor() {}
sendMessage(message: any) {
this.subject.next(message);
}
getData() {
return this.subject.asObservable();
}
}
I defined 2 method here. The first method using next() to send message to the next subcriber. So in your component you just need to simply subscribe like this to get the data
private subscription$: Subscription;
public ngOnInit(): void {
this.subscription$ = this.messageervice
.getData()
.subscribe(data => { console.log(data); })
}
public ngOnDestroy(): void {
this.subscription$.unsubscribe();
}
Found a workaround.
the basic idea is to register a custom event on the root frame of the app and listen on it from the other components
ComponentOne.ts
frameModule.topmost().notify({
eventName: 'punched',
object: frameModule.topmost(),
})
ComponentTwo.ts
frameModule.topmost().on('punched', () => {
console.log('event received');
})

Angular 2+ testing a component

I have the following service which make a call to an API :
export class MoviesService {
constructor(private httpClient: HttpClient) { }
// Return an obsevable with all the movies from the API
getAllMovies(): Observable<Movie[]> {
return this.httpClient.get<EmbeddedTitle>("http://localhost:8080/api/movies", { params: params })
.pipe(
map(response => {
return response;
}
));
}
And here's the component that inject that service and call the getAllMovies method:
export class MoviesListingComponent implements OnInit {
public movies: Movie[];
constructor(private moviesService: MoviesService) { }
ngOnInit(): void {
this.getAllMovies();
}
// Subscribe to method service (getAllMovies) and assign the results to a class object (movies)
getAllMovies(): void {
this.moviesService.getAllMovies()
.subscribe(movies => {
this.movies = movies;
});
}
}
I want to know how to unit testing the component's method getAllMovies with Jasmine.
the method getAllMovies() changes the value of your "movies" variable and you component has a dependency to the service that hold the data, your job is mock the service and create a getAllMovies that return dummy data and make sure that your movie field hold the data that you mock

Reading routing parameter in HTML

We can set the routing parameter through HTML:
<a [routerLink] = "['/api/foo/', id]"/>
I know that we can read routing parameter through handling event in the typescript:
import {OnInit, OnDestroy, Component} from '#angular/core';
#Component({...})
export class MyComponent implements OnInit{
constructor(private activatedRoute: ActivatedRoute) {}
ngOnInit() {
// subscribe to router event
this.activatedRoute.params.subscribe((params: Params) => {
let Id = params['id'];
console.log(Id);
});
}
}
However, is there any way to read route parameter in the HTML, not in the TypeScript component?
I would like to use in the following manner:
<a href="api/foo/[routerLink]"/>
If you want to get the param in html it is better to assign the param to a variable and use it in html
private param:number;
private ngOnInit() {
// subscribe to router event
this.activatedRoute.params.subscribe((params: Params) => {
this.param = params['id'];
console.log(this.param);
});
}
in your html
<div>{{param}}</div>
if you want to get id as number. You can use this.
id:number;
ngOnInit() {
this.subscription = this.activatedRoute.params.subscribe(
(params: any) => {
if (params.hasOwnProperty('id')) {
id= +params['id'];
//do whatever you want
}
}
);
}
and this destroyed the subscription
ngOnDestroy() {
this.subscription.unsubscribe();
}
and you can access the id field on html side.

Categories

Resources