Nolanus / ngx-page-scroll

Animated scrolling functionality for angular written in pure typescript
https://nolanus.github.io/ngx-page-scroll/
MIT License
477 stars 107 forks source link

Initiate scroll from parent component to target within router-outlet component #214

Closed entith closed 6 years ago

entith commented 7 years ago

I have a layout component with a <router-outlet> tag and a shared navbar. The navbar has links that should scroll to specific points within the current route's component. The links are populated using a shared service so the navbar knows whats can be linked to on the page. I have something like the following to display the links:

<li *ngFor="let link of quickLinks"><a pageScroll [attr.href]="link[0]">{{link[1]}}</a></li>

where quickLinks is an array of tuples containing the target ID tag and the display text.

When I try to click these links, I get an error that it cannot find the target to scroll to. I'm guessing this is because it lives in a different component.

Is it possible to achieve scrolling to targets within a the <router-outlet> tag?

Nolanus commented 7 years ago

Hi @entith,

the library checks the whole DOM tree for the target to scroll to. Thus it would also work with elements outside of any angular component.

Please double check that link[0] is a valid css selector (like #theId) and that a target with that exact ID is present in the current DOM tree.

If it still doesn't work, please consider creating a plunker to showcase the problem.

entith commented 7 years ago

Hello @Nolanus,

I set up a Plunker with a basic recreation of our setup and it works fine. I have no idea what is the effective difference between it any our actual application.

I have taken to trying to step through the JS execution of this module to see if anything seemed off. I've been specifically looking through the PageScrollService.start() method, and as far as I can tell, it runs the same for my problem links and for some working test links I added (links that point to targets within the same component rather than targets in a nested route's component). PageScrollInstance properties like distanceToScroll and targetScrollPosition seem to be calculating correctly.

Do you have any insights or ideas where I could look more closely?

Note that I am running the beta4 version of this module. I cannot update yet as I'd need to update the version of Angular we're using (which won't be for a bit).

Thanks for your help.

minirobotdan commented 7 years ago

Hi there, same issue here. My effective component hierarchy is as follows:

    <my-app>
        <app-header>
            <a href="#" (click)="myOnClick()"></href>
        </app-header>
        <router-outlet>
            <my-component>
                <div id="myTarget"></div>
            </my-component>
         </router-outlet>
    </my-app>

As with the OP, stepping into the service start execution seems to suggest all the correct values are being calculated, and I also cannot recreate this in a plunker, only in a non-trivial application. My header component code is as follows:

    onScrollDownClicked() {
    const pageScrollInstance: PageScrollInstance = PageScrollInstance.simpleInstance(this.document, '#myTarget');
    this.pageScrollService.start(pageScrollInstance);
    }

There are no caught or uncaught errors, modules are imported and services injected correctly, it just appears to silently fail. I've checked the DOM to ensure I can find the target element by ID, it's visible on the page. Any help would be appreciated, thanks.

minirobotdan commented 7 years ago

Quick update- mine turned out to be the use of "vh" units on my body tag to support a sticky footer.

Nolanus commented 7 years ago

You may always set PageScrollConfig._logLevel to 5 or higher to get verbose logging of the scroll positions in the console. This helps to identify whether it is a problem of starting the scroll animation or most likely a styling/DOM-structure issue (due to incorrect scrollingView element).

entith commented 7 years ago

I've finally got this working on my end. The quickLinks variable I was looping through was a getter property. When I changed to to regular variable, it worked fine. No idea why this makes a difference.

// This does not work for some reason.
get quickLinks(): [string, string][] {
  return [
    ["#case", "Case"],
    ["#progressnotes", "Progress Notes"],
    ["#closing", "Closing"],
    ["#review", "Review"]
  ];
}

// This works without problems
quickLinks: [string, string][] = [
  ["#case", "Case"],
  ["#progressnotes", "Progress Notes"],
  ["#closing", "Closing"],
  ["#review", "Review"]
];
entith commented 7 years ago

Quick note, it doesn't seem to like any function, doesn't matter if its a getter or not.

Nolanus commented 7 years ago

After a little testing it turns out the reason for that is the NgForOf component (you may fiddle around with that plunkr). It is based on instance comparison to identify changes. When using a function as source to iterate over, ngFor gets returned a new array every time the function runs and thus rebuilds the whole DOM structure. This results in the old links with pageScroll directive to be destroyed. The pageScroll directive automatically stops the scroll animation once it gets destroyed. You may put a breakpoint into the ngOnDestroy() method in the pageScroll directive to see that happening.

Solutions: Make sure your function always returns the same array instance, which should be better for performance anyway, or use an array like @entith already pointed out.