Navigate to a specified position on a dynamic Angular 2 page - html

If I have scrolled down on a page where multiple elements have been generated on *ngFor, clicking on an element brings me to a different page via the router.
When I click on a button to get back to the previous page, I would like the screen to be scrolled down to the position I was in before I left the page.
How can I achieve this?

Let's suppose List page has ngFor loop like:
<div *ngFor="let note of (noteService.notes | async); let i = index" class="item" tabindex="0">
<a (click)="edit(note, i, $event)">
{{note.text}}
</a>
</div>
In edit function, it provides additional index into url like ...?i=11:
this.router.navigate(['group', this.noteService.groupName, 'edit', note.$key],
{ queryParams: { i: index } }); // pass in additional index in ngFor loop
Edit page remembers index as this.noteIndex in ngOnInit, then when going back:
this.router.navigate(['group', this.noteService.groupName],
{ queryParams: { i: this.noteIndex } }); // to let List page focus this note
Then List page's ngOnInit can do below:
this.noteService.announcedCountNotes.subscribe(
count => {
// noteService announces new count after it saves edit
// so by now, ngFor should be ready
if (idxToFocus) { // this.route.snapshot.queryParams['i'];
setTimeout(_ => {
var elements = document.querySelectorAll('div.item');
var len = elements.length;
if (len > 0 && idxToFocus >= 0 && idxToFocus < len) {
const el = elements[idxToFocus] as HTMLElement;
//el.scrollIntoView();
el.focus();
}
}, 0);
}
}
)
You might want to provide specific style for div.item:focus, this way worked at least on Chrome 59 (Windows 7 and iPad) for me.

Related

Why Can't I Add The Same Image To Shopping Cart More Than Once

What I'm trying do is when the user adds item to cart more than once I would like it to be a separate image instead of just adding to the quantity. I have it now where if product exist in the cart it just adds to the quantity. The reason I need this change is because now there are product sizes associate with the product using radio buttons and this won't work the way it currently is. I tried manipulating the code but I still get same result or get cart is empty. Can someone point me in the right direction. I have included code snippet.
cart.service.ts
getCartItems(): Observable<CartItem[]> {
return this.http.get<CartItem[]>(cartUrl).pipe(
map((result: any[]) => {
let cartItems: CartItem[] =[];
for(let item of result) {
let productExists = false
for(let i in cartItems){
if(cartItems[i].productId === item.product.id){
cartItems[i].qty++
productExists = true
break;
}
}
if (!productExists){
cartItems.push( new CartItem(item.id,item.product,item.imageUrl));
}
}
return cartItems;
})
);
}
Thanking You In Advance
Are you sure you tried to modify the code?
In this chunk you are doing the quantity of the product and setting the flag for pushing or not the item with the image
for(let i in cartItems){
if(cartItems[i].productId === item.product.id){
cartItems[i].qty++
productExists = true
break;
}
In this other chunk you literally are reading the flag from the last "for" and pushing if the flag is false
if (!productExists){
cartItems.push( new CartItem(item.id,item.product,item.imageUrl));
}
You can do a for to push all the items without any validations or adding to the "qry"
I took out all the validation like Cayman suggested and now it works the way I need it to for the application. The previous code was checking if exist which I don't need because I want to load another product even if it does exist. Part of previous code is still good for situations when you don't want to add a product that already exist for example a wish list or favorites. Correct code snippet without validation below:
getCartItems(): Observable<CartItem[]> {
return this.http.get<CartItem[]>(cartUrl).pipe(
map((result: any[]) => {
let cartItems: CartItem[] =[];
for(let item of result) {
cartItems.push( new CartItem(item.id,item.product,item.imageUrl));
}
return cartItems;
})
);
}
PDH

How can I find the place in my code or page where the location is set?

I tried global event listeners pane in Chrome DevTools, I tried to put a debugger; inside document/window.addEventListener("unload", ...) and it is not working.
I tried to step over the statements in the file main.ts and nothing is breaking the code in there when I click on a link that should open another page than the one it is opening. I checked its HTML attributes and the correct URL is set in its href attribute. The link has a single class which is not used to open another page in the page's code as far as I know.
I also searched for all the places in my code where the (window.)location is changed.
I also updated npm packages using npm update.
I use KnockOut.js and I have this static HTML for the links that go to wrong pages:
<ul class="main-nav" data-bind="foreach: mainMenuItems">
<li>
<a data-bind="attr: { href: url, title: text }, text: text, css: { active: $data == $root.activeMenuItem() }"></a>
<div class="bg"></div>
</li>
</ul>
And this is a part of the TypeScript code (sorry for the ugly code, it is WIP):
let vm = new PageViewModel(null, "home", () => {
sammyApp = $.sammy(function () {
// big article URLs w/ date and slug
this.get(/\/(.+)\/(.+)\/(.+)\/(.+)\/(.*)[\/]?/, function() {
vm.language("ro");
vm.isShowingPage(false);
vm.isShowingHomePage(false);
let slug : string = this.params['splat'][3];
vm.slug(slug);
console.log('logging', { language: vm.language(), slug: vm.slug() });
vm.fetch();
vm.isShowingContactPage(false);
vm.activeMenuItem(vm.getMenuItemBySlug(slug));
});
// any other page
this.get(/\/ro\/(.+)\//, function () {
console.log('pseudo-navigating to /ro/etc.');
vm.language("ro");
vm.isShowingPage(true);
vm.isShowingHomePage(false);
let slug : string = this.params["splat"][0];
//slug = slug.substr(0, slug.length - 1);
if (slug !== 'contact') { // this page is in the default HTML, just hidden
vm.slug(slug);
vm.fetch();
vm.isShowingContactPage(false);
} else {
vm.isShowingContactPage(true);
window.scrollTo(0, 0);
}
vm.activeMenuItem(vm.getMenuItemBySlug(slug));
});
this.get(/\/en\/(.+)\//, function () {
console.log('pseudo-navigating to /en/etc.');
vm.language("en");
vm.isShowingPage(true);
vm.isShowingHomePage(false);
let slug : string = this.params["splat"][0];
//slug = slug.substr(0, slug.length - 1);
if (slug !== 'contact') { // this page is in the default HTML, just hidden
vm.slug(slug);
vm.fetch();
vm.isShowingContactPage(false);
} else {
vm.isShowingContactPage(true);
, () => {
uuuuucons
}9 function
window.scrollTo(0, 0);
}
vm.activeMenuItem(vm.getMenuItemBySlug(slug));
});
// the home page
this.get("/", function () {
console.log(`pseudo-navigating to /${vm.language()}/home`);
sammyApp.setLocation(`/${vm.language()}/home`);
});
});
sammyApp.run();
});
I have this code that catches the click event:
$("a").on("click", () => {
debugger;
});
But after this finding I do not know what I can do to find the source of the problem.
When the click is catched by the 3 LOCs above, I get this:
What could be the issue?
Thank you.
Update 1
After seeing these questions and their answers (the only thing I did not try was using an iframe):
How can I find the place in my code or page where the location is set?
Breakpoint right before page refresh?
Break javascript before an inline javascript redirect in Chrome
If I have a page for which I check the beforeunload and unload event checkboxes in the Event Listener Breakpoints pane in Chrome DevTools' tab Sources, and I click on a link which should not reload the page but it does, and the two breakpoints (beforeunload and unload) are not triggered in this process, what should I do next?
Is this a known bug? If so, can someone give me an URL?
Thank you.

Smoothly loading messages Angular 8

I'm writing sort of a chat application using Angular 8 and here's what I want to achieve:
My dialogue component that represents a chat between two users gets one page of last messages that consists of 10 messages after initiating. The div that contains these messages scrolls down to the very last message. When a user scrolls up and reaches a certain point the next page loads. The two arrays join and the user sees now 20 messages. Here's what I have so far:
HTML:
<div>
<div #scrollMe [scrollTop]="scrollMe.scrollHeight" (scroll)="onScroll($event)" style="overflow-y: scroll; height: 400px;">
<ul>
<li *ngFor="let message of messages?.reverse()">
</ul>
</div>
</div>
Typescipt:
loadMessages(page: number, itemsPerPage?: number) {
this.messageService.getMessageThread(page, itemsPerPage || 10)
.subscribe((res: PaginatedResult<MessageThread>) => {
if (this.messages == null) {
this.messages = res.result.messages;
} else {
this.messages = this.messages.concat(res.result.messages);
}
});
}
onScroll(event) {
if (event.target.scrollTop < 100) {
if (this.pagination.currentPage >= this.pagination.totalPages) {
return;
}
this.loadMessages(++this.pagination.currentPage);
}
}
It works, but the problem is that when I join these two arrays, my scrollbar jumps very ugly and since I hold the scrollbar it stays at the same position and keeps loading next pages. I am very new to Angular and front-end in general so I have a feeling that I'm missing something. I tried to find any ready-to-go solutions but could not. Any help would be appreciated.
Please note that I don't want to use JQuery.
Several things:
First, we need a loading flag:
loading = false;
Then we make loadMessages return an observable instead of handle the result:
loadMessages(page: number, itemsPerPage?: number) {
this.loading = true;
return this.messageService.getMessageThread(page, itemsPerPage || 10);
}
A separate method handleResponse handles the response by setting loading to false and concatenating the messages.
Then we can account for the request delay in the scroll handler and use the loading flag to prevent multiple requests:
onScroll(event) {
// get the scroll height before adding new messages
const startingScrollHeight = event.target.scrollHeight;
if (event.target.scrollTop < 100) {
if (this.pagination.currentPage >= this.pagination.totalPages) {
return;
}
else if (!this.loading) {
this.loadMessages(this.pagination.currentPage).subscribe((res) => {
this.handleResponse(res);
// using setTimeout lets the app "wait a beat" so it can measure
// new scroll height *after* messages are added
setTimeout(() => {
const newScrollHeight = this.scrollDiv.nativeElement.scrollHeight;
// set the scroll height from the difference of the new and starting scroll height
this.scrollDiv.nativeElement.scrollTo(0, newScrollHeight - startingScrollHeight);
});
});
}
}
}
Stackblitz (updated)

React Router v4 - How to detect back button navigation vs url refresh?

I have a url at /page (PAGE A) where I want to detect if the page was navigated to with history back from (PAGE B) or if the user was on (PAGE A) and manually refreshed the page from the URL bar refresh button (without using history back).
I looked into all the history, location, props by react router but didn't find a way to differentiate how the user navigated to the page.
In both scenarios, the history.action == 'POP' is the history action. Ideally it would be 'POP' when using the back button in the app to go back from page b to page a, and when on page a, when refreshing the page, it would be something other than 'POP' like 'REFRESH' for example.
How can we differentiate between both of them to run different logic in our app, since both trigger 'POP'?
Instead of comparing the history key, you can compare the pathname, for example, if you are in the page "/page1/page2" and hit refresh, the new location is the same. But if you hit the back action, the new location will be "/page1/".
This solution also uses a listener to listen to any action coming from history.
componentDidMount() {
const unlisten = history.listen((location, action) => {
if (action == 'POP') {
\\ thisLocation is the current location of your page
if (location.pathname != '/thisLocation/') {
alert('Back Pressed: ' + String(location.pathname));
} else {
alert('Refreshed: ' + String(location.pathname));
}
}
});
this.setState({ ...this.state, unlisten: unlisten });
}
componentWillUnmount() {
this.state.unlisten();
}
You can see more details in the link provided by Rei Dien as a comment of your question: https://www.npmjs.com/package/history
[EDIT]
Another way to do this is using https://www.npmjs.com/package/react-router-last-location and doing this:
import { useLastLocation } from 'react-router-last-location';
componentDidMount() {
const unlisten = history.listen((location, action) => {
const lastLocation = useLastLocation();
if (location.pathname == lastLocation.pathname) {
alert('Back Pressed: ' + String(location.pathname));
}
}
});
this.setState({ ...this.state, unlisten: unlisten });
}
componentWillUnmount() {
this.state.unlisten();
}
The downside is that there is no difference between activating the back action or clicking in a link that goes to the page that you was before, both would be detected as pressing back. If you don't want a new dependency, you can do it manually as stated in https://github.com/ReactTraining/react-router/issues/1066#issuecomment-412907443 creating a middleware.
I think this will at least point your in the right direction. Navigate to yourwebsite.com.
let current_page = history.state.key
if(history.action == 'POP') {
if(history.state.key == current_page) {
return 'page was refreshed'
}
return 'back button was pressed'
}

clicking on select writes on page

I'm building a site and I'm using angular material.
I have a select tag with this code:
<md-select ng-model="year">
<md-option><em></em></md-option>
<md-option ng-repeat="n in [] | rangeYear" value="{{$index+startYear}}">
{{$index+startYear}}
</md-option>
</md-select>
'startYear' and 'year' are fields in the scope. The code for rangeYear:
app.filter('rangeYear', function () {
return function (input) {
var years = [];
for (var i = 1924; i <= new Date().getFullYear(); i += 1) {
years.push(i);
}
return years;
}
});
Each time I click on the select element (in the page), [[object HTMLElement]]
is written in the top of the page.
does anyone know why does it happen? How can I prevent it?
EDIT:
I've uploaded the project to a server. This is thr link: http://rikudim.info/ang/
There is one dropdown list there.