I have an ng5-slider which has a range from 0 - 1000 and is adjustable by the user.
When I adjust the slider at the bottom of the page, the page refreshes and pulls me to the top of the page.
I would like to prevent that from occurring. How do I do this ?
Here is my code:
page.component.html
<div>
<ng5-slider
[(value)]="minValue"
[(highValue)]="maxValue"
(userChangeStart)="onUserChangeStart($event)"
(userChangeEnd)="onUserChangeEnd($event)"
[options]="options"
(userChange)="onUserChange($event)">
</ng5-slider>
</div>
page.component.ts
#Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.scss']
})
export class DetailComponent implements OnInit {
logText = '';
arr_price = [];
minValue = 0;
maxValue = 1000;
options: Options = {
floor: 0,
ceil: 1000
};
getChangeContextString(changeContext: ChangeContext): string {
return `${changeContext.value} ${changeContext.highValue}`;
}
onUserChangeStart(changeContext: ChangeContext): void {
this.logText = this.getChangeContextString(changeContext);
}
onUserChangeEnd(changeContext: ChangeContext): void {
this.logText = this.getChangeContextString(changeContext);
this.apply();
}
onUserChange(changeContext: ChangeContext): void {
this.logText = this.getChangeContextString(changeContext);
const value = this.logText;
const value_arr = value.split(' ');
this.arr_price = value_arr;
}
}
I've tried to remove (userChangeStart) and (userChangeEnd) and the page did not reload which is what I want.
However, when I manually click the refresh button, the slider values reverted back to its original 0 --------------- 1000.
I am thinking that I have to implement userChangeStart and userChangeEnd for data to be persisted but if only I can somehow prevent page reload from within these handler functions...
I fixed the bug by setting scrollPositionRestoration to disabled in the app routing module.
Initially it was set to enabled and thus any click event at the bottom of the page will bring the page to the top.
imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'disabled' })]
Related
i have two pages
APP COMPONENT HTML
<a routerLink="hello-page">HELLO PAGE</a>
<div></div>
<a routerLink="counter">COUNTER</a>
<router-outlet></router-outlet>
COUNTER HTML
<p>{{ counter }}</p>
<button (click)="onBtnClick()">Click</button>
COUNTER TS
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.css'],
})
export class CounterComponent implements OnInit {
constructor() {}
ngOnInit() {}
counter: number = 0;
onBtnClick() {
setInterval(() => {
this.counter++;
console.log('interval counter', this.counter);
}, 1000);
}
}
HELLO-PAGE HTML
<p>
hello-page works!
</p>
when I visit the counter page and when I click on the button - I am starting interval - inside i am increasing the value of my counter property on every one second so 1 2 3 4 etc.
Also the view is updated - we can see that 1 2 3 4 in the HTML.
While the interval is counting - I click on HELLO-PAGE link and I am routed to hello page component.
When I go on that page, I can see that the interval is still counting ( I did not cleared the interval when I destroyed the page in ngOnDestroy ) and he is still ticking.
After that when I go back on counter component again, the interval is still working and the counter is increasing BUT THE VIEW IS NOT UPDATED. {{ counter }} in the HTML is now 0.
I guess it is because the previos view is destroyed and now I have the new view.
I need to find a way where when I go back on that page I will get the value from the previous setInterval in my HTML.
So my HTML will be updated.
The full code you can find here
https://stackblitz.com/edit/angular-upmaek?file=src%2Fapp%2Fcounter%2Fcounter.component.ts,src%2Fapp%2Fcounter%2Fcounter.component.css,src%2Fapp%2Fcounter%2Fcounter.component.html,src%2Fapp%2Fapp-routing.module.ts,src%2Fapp%2Fapp.module.ts,src%2Fapp%2Fhello-page%2Fhello-page.component.html
you can stop interval.
CounterComponent.ts:
export class CounterComponent implements OnInit, OnDestroy {
constructor() {}
ngOnDestroy(): void {
clearInterval(this.intervalCount);/////
}
ngOnInit() {}
counter: number = 0;
intervalCount: any;
onBtnClick() {
this.intervalCount = setInterval(() => {
this.counter++;
console.log('interval counter', this.counter);
}, 1000);
}
}
You could move the interval into a service and in the CounterComponent inject the service and get the current counter value from it.
I have a list of elements that are rendered to the view using *ngFor, the number of elements in the list in most cases will exceed the height of the list container element, so overflow: auto being used to make the container scrollable.
Each element in the list represents the occurrence of an event at a specific time in the past, the occurrence time of the event stored in UNIX timestamp format.
On the core layer, there is a BehaviorSubject that starts from the first event timestamp and emits the next value every 100 milliseconds.
Back to the representation layer, there's a subscription of the SubjectBehavroure mentioned above, that does the following on each received new value:
Look for the event in the list, that match the newly received value
If an event is found, it should be activated
<div class="container" id="scroll">
<div
class="list-item"
id="{{ event.timestamp }}"
[ngClass]="{ active: event.active }"
*ngFor="let event of timelineEvents"
>
<div class="event-details text">{{ event.timestamp }}</div>
</div>
</div>
#Component({
selector: 'events',
templateUrl: './events.component.html',
styleUrls: ['./events.component.scss'],
})
export class EventsComponent implements OnInit {
timelineEvents: Array<FsTimeLineEvent> = [];
currentTimeSubscription$: Subscription;
startTimestamp: number;
constructor(private eventFacade: eventFacade) {}
ngOnInit(): void {
this.currentTimeSubscription$ = this.eventFacade.currentTime$.subscribe(
(currentTime) => {
// get current timestamp value
let currentTimestamp = currentTime + this.startTimestamp;
// look for an event
let currentEventIndex = this.timelineEvents.findIndex(
(x) => x.timestamp >= currentTimestamp
);
// set the event to active
this.setActiveEvent(currentEventIndex);
}
);
}
setActiveEvent(index) {
this.timelineEvents.forEach((eve) => {
eve.active = false;
});
let currentEvent = this.timelineEvents[index];
currentEvent.active = true;
let itemToScrollToView = document.getElementById(
`${currentEvent.timestamp}`
);
if (itemToScrollToView)
itemToScrollToView.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
}
How the event is activated?
Adding special CSS to it, simple border.
Scroll the event HTML Element into the center of the list container HTML element
The logic is working as expected, but only with scroll behavior set to 'auto'
let activeElement: HTMLElement = docment.getElementById(`${id}`)
activeElement.scrollIntoView({ behavior: 'auto', block: 'center' })
What I would love to achieve is smooth easy scrolling, I've tried to change the behavior of the scroll to 'smooth' but that disables the scrolling at all. Which makes no sense to me at all, like why this is happening?
I've made a ton of researches, I've tried to implement my own custom scrollIntoView function, but I've got nothing! I would appreciate your help! Thanks in advance!
Currently, I'm trying to automatically scroll to the top of the HTML page for which I'm using in my Typescript.
window.scrollTo(0 , 0);
and while trying to automatically scroll down to bottom of the HTML page
window.scrollTo( 0 , document.body.scrollHeight);
I'm trying to scroll top after an HTTP response.
Code
openPDFVievwer(data) {
this.obj= JSON.parse(data._body);
document.getElementById('spinner').style.display = 'none';
window.scrollTo( 0 , 0);
}
when I'm trying to scroll bottom after rendering another component.
Code
searchData(data) {
this.document = data;
this.searchResultDiv = true; // where component will be rendered
window.scrollTo( 0 , document.body.scrollHeight);
}
but, both seem to be not working.
Is there something that I'm doing wrong?
try into html
<div #list [scrollTop]="list.scrollHeight"></div>
Solution 2
In Component
define id into html id="scrollId"
const element = document.querySelector('#scrollId');
element.scrollIntoView();
Answer for angular 2+
It's very simple,
Just create an any element
e.g.
<span id="moveTop"></span> or add just id into the element or use already existed Id where you have to move top, down, mid etc.
and add this method on specific event, like I want to move top when edit as my list list too much.
gotoTop() {
var scrollElem= document.querySelector('#moveTop');
scrollElem.scrollIntoView();
}
or If you want to send Id as Parameter you simply just create Optional Parameter
gotoTop(elementId?: string) {
if (elementId != null) {
var element = document.querySelector(elementId);
element.scrollIntoView();
}
else {
var element = document.querySelector('#moveTop');
element.scrollIntoView();
}
}
Above solution wasn't working for me, Try this
code:
import { Router, NavigationEnd } from '#angular/router';
constructor(private router: Router)
ngOnInit()
{
this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) {
return;
}
document.getElementsByTagName("app-website-nav")[0].scrollIntoView();
});
}
I have an angular app which has user login and logout. I am showing up a welcome page as the home page before a user logs in. I want to enable a background image only on the welcome page. Once the user logs in, the background image must disappear. When the user logs out, he will be redirected to welcome page which must show up the background image again.
I have tried using #HostBinding in the app.component.ts by renaming the selector to 'body'.
app.component.ts
import {Component, HostBinding, Input} from '#angular/core';
import {InputMask} from "primeng/primeng";
#Component({
selector: 'body',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
path = '../assets/img/AvayaHome.jpg';
title = 'app';
toggleClass = true;
#HostBinding('class.bodyClass') isWelcomePage = this.toggleClass;
}
Here is my CSS.
app.component.css
.bodyClass {
background-image: url("../assets/img/AvayaHome.jpg");
}
Here is my index.html
<!doctype html>
<html lang="en">
<head>
<title> Something </title>
</head>
<body class="bodyClass">
<app-welcome-page></app-welcome-page>
</body>
</html>
I am enabling the css style for bodyClass by assigning toggleClass as true. Once the user logs in, I am changing the value of toggleClass (which is in the app.component.ts) from the child component.
Here is my login.component.ts
onLogin() {
console.log('onLogin() invoked:', this._email, ':' , this.password);
if (this._email == null || this.password == null) {
this.errorMessage = 'All fields are required';
return;
}
this.errorMessage = null;
this.loginservice.authenticate(this._email, this.password);
this.appComponent.toggleClass = true;
this.router.navigate(['/dashboard']);
}
The value of the toggleClass changes when the user logs in to FALSE. But I am still seeing the background image. Not sure what I am doing wrong. Any help will be appreciated.
As an example, let's take a look at this code:
var toggleClass = false;
var isWelcomePage = toggleClass;
console.log(isWelcomePage); // prints true
Cool, all works as expected.
Ten seconds later....
Some user logins:
toggleClass = true;
console.log(isWelcomePage); // prints false
Why it has not changed???
If you open any documentation or any book about javascript you can read one main rule:
Primitives are always immutable.
When we assign toggleClass variable to isWelcomePage variable using =, we copy the value to the new variable because primitives are copied by value.
Solution 1:
Change isWelcomePage property directly
onLogin() {
...
this.appComponent.isWelcomePage = true;
...
}
Solution 2
Define getter
#HostBinding('class.bodyClass')
get isWelcomePage() {
return this.toggleClass;
}
Make a function with if and else;
if (user is login) {
document.body.classList.add('bodyClass');
} else {
document.body.classList.remove('bodyClass');
}
Than call that function when ever you need, logIn logOut etc
If you want to dynamically display and hide a background you should use a conditional class with ngClass
You can read more about it here NgClass
In your case it would be
<div [ngClass]="{'bodyClass': isWelcomePage}">
...
</div>
Then bodyClass css class will only apply IF isWelcomePage is true, if it's false it won't apply and the image won't show.
Edit:
As requested, a working example: Plunkr
Hostbinding only binds stuff to host tag, in your case tag.
So if you want to manipulate the body tag, you have to do it using plan javascript from your component or also create a component in the body.
#Component({
selector: 'body',
template: `<child></child>`
})
export class AppComponent {
#HostBinding('class') public cssClass = 'class1';
}
So, basically I need to reload my component after id of url parameter was changed. This is my player.component.ts:
import {Component, OnInit, AfterViewInit} from '#angular/core';
import {ActivatedRoute, Router} from '#angular/router';
declare var jQuery: any;
#Component({
selector: 'video-player',
templateUrl: './player.component.html',
styleUrls: ['./player.component.less']
})
export class VideoPlayerComponent implements OnInit, AfterViewInit {
playerTop: number;
currentVideoId: number;
constructor(
private _route: ActivatedRoute,
private _router: Router
) {
}
ngOnInit(): void {
this._route.params.subscribe(params => {
this.currentVideoId = +params['id'];
console.log( this.currentVideoId );
this._router.navigate(['/video', this.currentVideoId]);
});
}
ngAfterViewInit(): void {
if (this.videoPageParams(this.currentVideoId)) {
console.log( "afterViewInit" );
let params = this.videoPageParams(this.currentVideoId);
let fakeVideoItemsCount = Math.floor(params.containerWidth / params.videoItemWidth);
this.insertFakeVideoItems( this.currentVideoId, fakeVideoItemsCount);
this.changePlayerPosition( params.videoItemTop );
}
}
videoPageParams( id ): any {
let videoItemTop = jQuery(`.videoItem[data-id="${id}"]`).position().top;
let videoItemWidth = jQuery('.videoItem').width();
let containerWidth = jQuery('.listWrapper').width();
return {
videoItemTop,
videoItemWidth,
containerWidth
};
}
changePlayerPosition( videoItemTop ): void {
this.playerTop = videoItemTop;
}
insertFakeVideoItems( id, fakeVideoItemsCount ): void {
let fakeVideoItemHTML = `<div class="videoItem fake"></div>`;
let html5playerHeight = jQuery('#html5player').height();
let videoItemIndex = jQuery(`.videoItem[data-id="${id}"]`).index() + 1;
let videoItemInsertAfterIndex;
let videoItemRow = Math.ceil(videoItemIndex / fakeVideoItemsCount);
let videoItemRowBefore = videoItemRow - 1;
if ( videoItemIndex <= 4 ) {
videoItemInsertAfterIndex = 0;
} else {
videoItemInsertAfterIndex = (videoItemRowBefore * fakeVideoItemsCount);
}
let videoItemInsertAfter = jQuery('.videoItem').eq(videoItemInsertAfterIndex);
for ( let i = 0; i < fakeVideoItemsCount; i++ ) {
$(fakeVideoItemHTML).insertBefore(videoItemInsertAfter);
}
jQuery(`.videoItem.fake`).css('height', html5playerHeight);
}
}
player.component.html:
<video
class="video"
preload="auto"
[attr.data-id]="currentVideoId"
src="">
</video>
<videos-list></videos-list>
videoList.component.html
<div class="videoItem" *ngFor="let video of videos" [attr.data-id]="video.id">
<a [routerLink]="['/video', video.id]">
<img [src]='video.thumbnail' alt="1">
</a>
</div>
So when I click <a [routerLink]="['/video', video.id]"> in videoList.component.html it changes route to /video/10 for example, but the part from player.component.ts which manipulates the DOM doesn't fire again - DOM manipulation doesn't update.
I tried to manually navigate to route via this._router.navigate(['/video', this.currentVideoId]); but somehow it doesn't work.
QUESTION
Is there any way to run functions that manipulate DOM each time route param changes in the same URL?
DOM will not update because ngOnInit is only fired once, so it will not update even if you try to "renavigate" back to the parent from the child, since the parent haven't been removed from the DOM at any point.
One option to solve this, is that you could use a Subject, that when the routing is happening, let's send the chosen video id to parent, which subscribes to the change and does whatever you tell it to do, meaning calling functions that will update the DOM, so probably what you want re-executed is the inside ngOnInit and ngAfterViewInit
You mentioned that you had tried using
this._router.navigate(['/video', this.currentVideoId])
so let's look at that. Probably have some click event that fires a function. Let's say it looks like the following, we'll just add the subject in the play
navigate(id) {
VideoPlayerComponent.doUpdate.next(id)
this._router.navigate(['/video', this.currentVideoId])
}
Let's declare the Subject in your parent, and subscribe to the changes:
public static doUpdate: Subject<any> = new Subject();
and in the constructor let's subscribe to the changes...
constructor(...) {
VideoPlayerComponent.doUpdate.subscribe(res => {
console.log(res) // you have your id here
// re-fire whatever functions you need to update the DOM
});
}