Skip to content

Commit

Permalink
refactor(module:anchor): refactor (#4877)
Browse files Browse the repository at this point in the history
* refactor(module:anchor): refactor

close #4553

* refactor(module:anchor): use renderer to set classname

* fix(module:back-top): fix visible changes
  • Loading branch information
hsuanxyz authored Mar 15, 2020
1 parent 0bcd2a9 commit e7fa38e
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import { Platform } from '@angular/cdk/platform';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ElementRef,
Expand All @@ -18,19 +17,22 @@ import {
OnInit,
Renderer2,
TemplateRef,
ViewChild,
ViewEncapsulation
} from '@angular/core';

import { NzAnchorComponent } from './nz-anchor.component';
import { NzAnchorComponent } from './anchor.component';

@Component({
selector: 'nz-link',
exportAs: 'nzLink',
preserveWhitespaces: false,
templateUrl: './nz-anchor-link.component.html',
host: {
'[class.ant-anchor-link-active]': 'active'
},
template: `
<a #linkTitle (click)="goToClick($event)" href="{{ nzHref }}" class="ant-anchor-link-title" title="{{ titleStr }}">
<span *ngIf="titleStr; else titleTpl || nzTemplate">{{ titleStr }}</span>
</a>
<ng-content></ng-content>
`,
styles: [
`
nz-link {
Expand All @@ -47,7 +49,6 @@ export class NzAnchorLinkComponent implements OnInit, OnDestroy {
titleStr: string | null = '';
// tslint:disable-next-line:no-any
titleTpl: TemplateRef<any>;
active: boolean = false;

@Input()
set nzTitle(value: string | TemplateRef<void>) {
Expand All @@ -60,21 +61,33 @@ export class NzAnchorLinkComponent implements OnInit, OnDestroy {
}

@ContentChild('nzTemplate', { static: false }) nzTemplate: TemplateRef<void>;
@ViewChild('linkTitle') linkTitle: ElementRef<HTMLAnchorElement>;

constructor(
public elementRef: ElementRef,
private anchorComp: NzAnchorComponent,
private cdr: ChangeDetectorRef,
private platform: Platform,
renderer: Renderer2
private renderer: Renderer2
) {
renderer.addClass(elementRef.nativeElement, 'ant-anchor-link');
this.renderer.addClass(elementRef.nativeElement, 'ant-anchor-link');
}

ngOnInit(): void {
this.anchorComp.registerLink(this);
}

getLinkTitleElement(): HTMLAnchorElement {
return this.linkTitle.nativeElement;
}

setActive(): void {
this.renderer.addClass(this.elementRef.nativeElement, 'ant-anchor-link-active');
}

unsetActive(): void {
this.renderer.removeClass(this.elementRef.nativeElement, 'ant-anchor-link-active');
}

goToClick(e: Event): void {
e.preventDefault();
e.stopPropagation();
Expand All @@ -83,10 +96,6 @@ export class NzAnchorLinkComponent implements OnInit, OnDestroy {
}
}

markForCheck(): void {
this.cdr.markForCheck();
}

ngOnDestroy(): void {
this.anchorComp.unregisterLink(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,30 @@ import {
EventEmitter,
Inject,
Input,
NgZone,
OnChanges,
OnDestroy,
Output,
Renderer2,
SimpleChanges,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { fromEvent, Subscription } from 'rxjs';
import { distinctUntilChanged, throttleTime } from 'rxjs/operators';
import { fromEvent, Subject } from 'rxjs';
import { takeUntil, throttleTime } from 'rxjs/operators';

import { InputBoolean, InputNumber, NgStyleInterface, NzConfigService, NzScrollService, toNumber, WithConfig } from 'ng-zorro-antd/core';

import { NzAnchorLinkComponent } from './nz-anchor-link.component';
import {
InputBoolean,
InputNumber,
NgStyleInterface,
NzConfigService,
NzScrollService,
warnDeprecation,
WithConfig
} from 'ng-zorro-antd/core';

import { NzAnchorLinkComponent } from './anchor-link.component';
import { getOffsetTop } from './util';

interface Section {
comp: NzAnchorLinkComponent;
Expand All @@ -41,11 +54,25 @@ const sharpMatcherRegx = /#([^#]+)$/;
selector: 'nz-anchor',
exportAs: 'nzAnchor',
preserveWhitespaces: false,
templateUrl: './nz-anchor.component.html',
template: `
<nz-affix *ngIf="nzAffix; else content" [nzOffsetTop]="nzOffsetTop" [nzTarget]="container">
<ng-template [ngTemplateOutlet]="content"></ng-template>
</nz-affix>
<ng-template #content>
<div class="ant-anchor-wrapper" [ngStyle]="wrapperStyle">
<div class="ant-anchor" [ngClass]="{ fixed: !nzAffix && !nzShowInkInFixed }">
<div class="ant-anchor-ink">
<div class="ant-anchor-ink-ball" #ink></div>
</div>
<ng-content></ng-content>
</div>
</div>
</ng-template>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NzAnchorComponent implements OnDestroy, AfterViewInit {
export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges {
@ViewChild('ink', { static: false }) private ink: ElementRef;

@Input() @InputBoolean() nzAffix = true;
Expand All @@ -61,45 +88,35 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit {
nzBounds: number;

@Input()
@InputNumber()
@WithConfig<number>(NZ_CONFIG_COMPONENT_NAME)
set nzOffsetTop(value: number) {
this._offsetTop = toNumber(value, 0);
this.wrapperStyle = {
'max-height': `calc(100vh - ${this._offsetTop}px)`
};
}
nzOffsetTop: number;

get nzOffsetTop(): number {
return this._offsetTop;
}

private _offsetTop: number;

@Input()
set nzTarget(el: string | Element) {
this.target = typeof el === 'string' ? this.doc.querySelector(el) : el;
this.registerScrollEvent();
}
@Input() nzContainer: string | HTMLElement;
@Input() nzTarget: string | HTMLElement;

@Output() readonly nzClick = new EventEmitter<string>();
@Output() readonly nzScroll = new EventEmitter<NzAnchorLinkComponent>();

visible = false;
wrapperStyle: NgStyleInterface = { 'max-height': '100vh' };

container: HTMLElement | Window;

private links: NzAnchorLinkComponent[] = [];
private animating = false;
private target: Element | null = null;
private scroll$: Subscription | null = null;
private destroyed = false;
private destroy$ = new Subject();
private handleScrollTimeoutID = -1;

constructor(
public nzConfigService: NzConfigService,
private scrollSrv: NzScrollService,
/* tslint:disable-next-line:no-any */
@Inject(DOCUMENT) private doc: any,
public nzConfigService: NzConfigService,
private scrollSrv: NzScrollService,
private cdr: ChangeDetectorRef,
private platform: Platform
private platform: Platform,
private zone: NgZone,
private renderer: Renderer2
) {}

registerLink(link: NzAnchorLinkComponent): void {
Expand All @@ -110,54 +127,37 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit {
this.links.splice(this.links.indexOf(link), 1);
}

private getTarget(): Element | Window {
return this.target || window;
private getContainer(): HTMLElement | Window {
return this.container || window;
}

ngAfterViewInit(): void {
this.registerScrollEvent();
}

ngOnDestroy(): void {
this.destroyed = true;
this.removeListen();
clearTimeout(this.handleScrollTimeoutID);
this.destroy$.next();
this.destroy$.complete();
}

private registerScrollEvent(): void {
if (!this.platform.isBrowser) {
return;
}
this.removeListen();
this.scroll$ = fromEvent(this.getTarget(), 'scroll')
.pipe(throttleTime(50), distinctUntilChanged())
.subscribe(() => this.handleScroll());
this.destroy$.next();
this.zone.runOutsideAngular(() => {
fromEvent(this.getContainer(), 'scroll')
.pipe(throttleTime(50), takeUntil(this.destroy$))
.subscribe(() => this.handleScroll());
});
// Browser would maintain the scrolling position when refreshing.
// So we have to delay calculation in avoid of getting a incorrect result.
setTimeout(() => this.handleScroll());
}

private removeListen(): void {
if (this.scroll$) {
this.scroll$.unsubscribe();
}
}

private getOffsetTop(element: HTMLElement): number {
if (!element || !element.getClientRects().length) {
return 0;
}
const rect = element.getBoundingClientRect();
if (rect.width || rect.height) {
if (this.getTarget() === window) {
return rect.top - element.ownerDocument!.documentElement!.clientTop;
}
return rect.top - (this.getTarget() as HTMLElement).getBoundingClientRect().top;
}
return rect.top;
this.handleScrollTimeoutID = setTimeout(() => this.handleScroll());
}

handleScroll(): void {
if (typeof document === 'undefined' || this.destroyed || this.animating) {
if (typeof document === 'undefined' || this.animating) {
return;
}

Expand All @@ -170,7 +170,7 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit {
}
const target = this.doc.getElementById(sharpLinkMatch[1]);
if (target) {
const top = this.getOffsetTop(target);
const top = getOffsetTop(target, this.getContainer());
if (top < scope) {
sections.push({
top,
Expand All @@ -188,43 +188,68 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit {
const maxSection = sections.reduce((prev, curr) => (curr.top > prev.top ? curr : prev));
this.handleActive(maxSection.comp);
}
this.setVisible();
}

private clearActive(): void {
this.links.forEach(i => {
i.active = false;
i.markForCheck();
i.unsetActive();
});
}

private handleActive(comp: NzAnchorLinkComponent): void {
this.clearActive();

comp.active = true;
comp.markForCheck();

const linkNode = (comp.elementRef.nativeElement as HTMLDivElement).querySelector('.ant-anchor-link-title') as HTMLElement;
comp.setActive();
const linkNode = comp.getLinkTitleElement();
this.ink.nativeElement.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
this.visible = true;
this.cdr.detectChanges();

this.setVisible();
this.nzScroll.emit(comp);
}

private setVisible(): void {
const visible = this.visible;
const visibleClassname = 'visible';
if (this.ink) {
if (visible) {
this.renderer.addClass(this.ink.nativeElement, visibleClassname);
} else {
this.renderer.removeClass(this.ink.nativeElement, visibleClassname);
}
}
}

handleScrollTo(linkComp: NzAnchorLinkComponent): void {
const el = this.doc.querySelector(linkComp.nzHref);
if (!el) {
return;
}

this.animating = true;
const containerScrollTop = this.scrollSrv.getScroll(this.getTarget());
const elOffsetTop = this.getOffsetTop(el);
const containerScrollTop = this.scrollSrv.getScroll(this.getContainer());
const elOffsetTop = getOffsetTop(el, this.getContainer());
const targetScrollTop = containerScrollTop + elOffsetTop - (this.nzOffsetTop || 0);
this.scrollSrv.scrollTo(this.getTarget(), targetScrollTop, undefined, () => {
this.scrollSrv.scrollTo(this.getContainer(), targetScrollTop, undefined, () => {
this.animating = false;
this.handleActive(linkComp);
});
this.nzClick.emit(linkComp.nzHref);
}

ngOnChanges(changes: SimpleChanges): void {
const { nzOffsetTop, nzTarget, nzContainer } = changes;
if (nzOffsetTop) {
this.wrapperStyle = {
'max-height': `calc(100vh - ${this.nzOffsetTop}px)`
};
}
if (nzContainer || nzTarget) {
const container = this.nzContainer || this.nzTarget;
this.container = typeof container === 'string' ? this.doc.querySelector(container) : container;
this.registerScrollEvent();
if (nzTarget) {
warnDeprecation(`'nzTarget' of 'nz-anchor' is deprecated and will be removed in 10.0.0.Please use 'nzContainer' instead.`);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { NgModule } from '@angular/core';
import { NzAffixModule } from 'ng-zorro-antd/affix';
import { SCROLL_SERVICE_PROVIDER } from 'ng-zorro-antd/core';

import { NzAnchorLinkComponent } from './nz-anchor-link.component';
import { NzAnchorComponent } from './nz-anchor.component';
import { NzAnchorLinkComponent } from './anchor-link.component';
import { NzAnchorComponent } from './anchor.component';

@NgModule({
declarations: [NzAnchorComponent, NzAnchorLinkComponent],
Expand Down
Loading

0 comments on commit e7fa38e

Please sign in to comment.