Skip to content

Commit fae88a5

Browse files
committed
fix(plugin): index.component to support AOT
1 parent 24238e8 commit fae88a5

File tree

3 files changed

+120
-144
lines changed

3 files changed

+120
-144
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"__symbolic":"module","version":1,"metadata":{"ScrollSpyIndexRenderComponent":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component"},"arguments":[{"selector":"scrollSpy-index-render","template":"<div #container></div>","changeDetection":{"__symbolic":"select","expression":{"__symbolic":"reference","module":"@angular/core","name":"ChangeDetectionStrategy"},"member":"OnPush"}}]}],"members":{"scrollSpyIndexRenderOptions":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"viewContainerRef":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"ViewChild"},"arguments":["container",{"read":{"__symbolic":"reference","module":"@angular/core","name":"ViewContainerRef"}}]}]}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"Compiler"},{"__symbolic":"reference","module":"@angular/core","name":"ChangeDetectorRef"},{"__symbolic":"reference","module":"@angular/core","name":"ComponentFactoryResolver"},{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"},{"__symbolic":"reference","module":"../index","name":"ScrollSpyService"},{"__symbolic":"reference","module":"./index.service","name":"ScrollSpyIndexService"}]}],"ngOnInit":[{"__symbolic":"method"}],"ngAfterViewInit":[{"__symbolic":"method"}],"update":[{"__symbolic":"method"}],"itemConstruct":[{"__symbolic":"method"}],"calculateHighlight":[{"__symbolic":"method"}],"getItemsToHighlight":[{"__symbolic":"method"}],"compileToComponent":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}}
1+
{"__symbolic":"module","version":1,"metadata":{"ScrollSpyIndexRenderComponent":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable"}},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component"},"arguments":[{"selector":"scrollSpy-index-render","template":"\n <div #container>\n <ul class=\"nav menu\">\n <li *ngFor=\"let item of items\" [class.active]=\"highlight(item.link)\">\n <a [routerLink]=\"\" fragment=\"{{item.link}}\" (click)=\"goTo(item.link)\">{{item.text}}</a>\n <ul *ngIf=\"item.children.length\" class=\"nav menu\">\n <li *ngFor=\"let itemChild of item.children\" [class.active]=\"highlight(itemChild.link)\">\n <a [routerLink]=\"\" fragment=\"{{itemChild.link}}\" (click)=\"goTo(itemChild.link)\">{{itemChild.text}}</a>\n <ul *ngIf=\"itemChild.children.length\" class=\"nav menu\">\n <li *ngFor=\"let itemChild1 of itemChild.children\" [class.active]=\"highlight(itemChild1.link)\">\n <a [routerLink]=\"\" fragment=\"{{itemChild1.link}}\" (click)=\"goTo(itemChild1.link)\">{{itemChild1.text}}</a>\n <ul *ngIf=\"itemChild1.children.length\" class=\"nav menu\">\n <li *ngFor=\"let itemChild2 of itemChild1.children\" [class.active]=\"highlight(itemChild2.link)\">\n <a [routerLink]=\"\" fragment=\"{{itemChild2.link}}\" (click)=\"goTo(itemChild2.link)\">{{itemChild2.text}}</a>\n </li>\n </ul>\n </li>\n </ul>\n </li>\n </ul>\n </li>\n </ul>\n </div>\n ","changeDetection":{"__symbolic":"select","expression":{"__symbolic":"reference","module":"@angular/core","name":"ChangeDetectionStrategy"},"member":"OnPush"}}]}],"members":{"scrollSpyIndexRenderOptions":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"ChangeDetectorRef"},{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"},{"__symbolic":"reference","module":"../index","name":"ScrollSpyService"},{"__symbolic":"reference","module":"./index.service","name":"ScrollSpyIndexService"}]}],"ngOnInit":[{"__symbolic":"method"}],"ngAfterViewInit":[{"__symbolic":"method"}],"update":[{"__symbolic":"method"}],"calculateHighlight":[{"__symbolic":"method"}],"highlight":[{"__symbolic":"method"}],"goTo":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}}

src/plugin/index.component.spec.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ describe('plugin index.render.directive', () => {
3838
advance(fixture);
3939

4040
let compiled = fixture.debugElement.nativeElement;
41-
let match = compiled.getElementsByTagName('scrollspymenu')[0].outerHTML;
41+
let match = compiled.getElementsByTagName('scrollspy-index-render')[0].outerHTML;
4242

43-
expect(match).toEqual('<scrollspymenu><ul class="nav menu"><li pagemenuspy="test1" parent="" class="active"><a fragment="test1" ng-reflect-fragment="test1" ng-reflect-href="/#test1" href="/#test1">test1</a></li><li pagemenuspy="test2" parent=""><a fragment="test2" ng-reflect-fragment="test2" ng-reflect-href="/#test2" href="/#test2">test2</a></li><li pagemenuspy="test3" parent=""><a fragment="test3" ng-reflect-fragment="test3" ng-reflect-href="/#test3" href="/#test3">test3</a></li></ul></scrollspymenu>');
43+
expect(match).toContain('<a ng-reflect-fragment="test1" ng-reflect-href="/#test1" href="/#test1">test1</a>');
44+
expect(match).toContain('<a ng-reflect-fragment="test2" ng-reflect-href="/#test2" href="/#test2">test2</a>');
45+
expect(match).toContain('<a ng-reflect-fragment="test3" ng-reflect-href="/#test3" href="/#test3">test3</a>');
46+
expect(match).toContain('<a ng-reflect-fragment="test3-child" ng-reflect-href="/#test3-child" href="/#test3-child">test3-child</a>');
47+
expect(match).toContain('<a ng-reflect-fragment="test3-child-child" ng-reflect-href="/#test3-child-child" href="/#test3-child-child">test3-child-child</a>');
4448
})));
4549

4650
it('should highlight base on spyId',
@@ -57,15 +61,18 @@ describe('plugin index.render.directive', () => {
5761
advance(fixture);
5862

5963
let match = compiled.getElementsByClassName('active')[0].outerHTML;
60-
expect(match).toEqual('<li pagemenuspy="test1" parent="" class="active"><a fragment="test1" ng-reflect-fragment="test1" ng-reflect-href="/#test1" href="/#test1">test1</a></li>');
64+
expect(match).toContain('<a ng-reflect-fragment="test1" ng-reflect-href="/#test1" href="/#test1">test1</a>');
6165

62-
window.scrollTo(0, 1100);
66+
window.scrollTo(0, 3100);
6367
evt.initUIEvent('scroll', true, true, window, 1);
6468
window.dispatchEvent(evt);
6569
advance(fixture);
6670

67-
match = compiled.getElementsByClassName('active')[0].outerHTML;
68-
expect(match).toEqual('<li pagemenuspy="test2" parent="" class="active"><a fragment="test2" ng-reflect-fragment="test2" ng-reflect-href="/#test2" href="/#test2">test2</a></li>');
71+
match = compiled.getElementsByClassName('active');
72+
73+
expect(match.length).toEqual(2);
74+
expect(match[0].outerHTML).toContain('<a ng-reflect-fragment="test3" ng-reflect-href="/#test3" href="/#test3">test3</a>');
75+
expect(match[1].outerHTML).toContain('<a ng-reflect-fragment="test3-child" ng-reflect-href="/#test3-child" href="/#test3-child">test3-child</a>');
6976
})));
7077

7178
it('should highlight respecting topMargin',
@@ -82,15 +89,15 @@ describe('plugin index.render.directive', () => {
8289
advance(fixture);
8390

8491
let match = compiled.getElementsByClassName('active')[0].outerHTML;
85-
expect(match).toEqual('<li pagemenuspy="test1" parent="" class="active"><a fragment="test1" ng-reflect-fragment="test1" ng-reflect-href="/#test1" href="/#test1">test1</a></li>');
92+
expect(match).toContain('<a ng-reflect-fragment="test1" ng-reflect-href="/#test1" href="/#test1">test1</a>');
8693

8794
window.scrollTo(0, 900);
8895
evt.initUIEvent('scroll', true, true, window, 1);
8996
window.dispatchEvent(evt);
9097
advance(fixture);
9198

9299
match = compiled.getElementsByClassName('active')[0].outerHTML;
93-
expect(match).toEqual('<li pagemenuspy="test2" parent="" class="active"><a fragment="test2" ng-reflect-fragment="test2" ng-reflect-href="/#test2" href="/#test2">test2</a></li>');
100+
expect(match).toContain('<a ng-reflect-fragment="test2" ng-reflect-href="/#test2" href="/#test2">test2</a>');
94101
})));
95102
});
96103

@@ -102,6 +109,11 @@ describe('plugin index.render.directive', () => {
102109
<h3 id="test1" class="anchor" style="height: 1000px;">test1</h3>
103110
<h3 id="test2" class="anchor" style="height: 1000px;">test2</h3>
104111
<h3 id="test3" class="anchor" style="height: 1000px;">test3</h3>
112+
<h4 id="test3-child" class="anchor" style="height: 1000px;">test3-child</h4>
113+
<h5 id="test3-child-child" class="anchor" style="height: 1000px;">test3-child-child</h5>
114+
<h3 id="test4" class="anchor" style="height: 1000px;">test4</h3>
115+
<h4 id="test4-child" class="anchor" style="height: 1000px;">test4-child</h4>
116+
<h5 id="test4-child-child" class="anchor" style="height: 1000px;">test4-child-child</h5>
105117
</div>
106118
<scrollSpy-index-render [scrollSpyIndexRenderOptions]="{id: 'test', spyId: 'window', topMargin: -200}"></scrollSpy-index-render>
107119
</div>

src/plugin/index.component.ts

Lines changed: 99 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import {
2-
NgModule,
32
Component,
4-
Compiler,
53
ChangeDetectorRef,
64
Injectable,
75
Input,
86
ElementRef,
9-
ComponentFactoryResolver,
107
OnInit,
118
AfterViewInit,
12-
ViewContainerRef,
13-
ViewChild,
149
OnDestroy,
1510
ChangeDetectionStrategy
1611
} from '@angular/core';
17-
import { RouterModule } from '@angular/router';
1812

1913
import { ScrollSpyService } from '../index';
2014
import { ScrollSpyIndexService } from './index.service';
@@ -28,21 +22,39 @@ export interface ScrollSpyIndexComponentOptions {
2822
@Injectable()
2923
@Component({
3024
selector: 'scrollSpy-index-render',
31-
template: `<div #container></div>`,
25+
template: `
26+
<div #container>
27+
<ul class="nav menu">
28+
<li *ngFor="let item of items" [class.active]="highlight(item.link)">
29+
<a [routerLink]="" fragment="{{item.link}}" (click)="goTo(item.link)">{{item.text}}</a>
30+
<ul *ngIf="item.children.length" class="nav menu">
31+
<li *ngFor="let itemChild of item.children" [class.active]="highlight(itemChild.link)">
32+
<a [routerLink]="" fragment="{{itemChild.link}}" (click)="goTo(itemChild.link)">{{itemChild.text}}</a>
33+
<ul *ngIf="itemChild.children.length" class="nav menu">
34+
<li *ngFor="let itemChild1 of itemChild.children" [class.active]="highlight(itemChild1.link)">
35+
<a [routerLink]="" fragment="{{itemChild1.link}}" (click)="goTo(itemChild1.link)">{{itemChild1.text}}</a>
36+
<ul *ngIf="itemChild1.children.length" class="nav menu">
37+
<li *ngFor="let itemChild2 of itemChild1.children" [class.active]="highlight(itemChild2.link)">
38+
<a [routerLink]="" fragment="{{itemChild2.link}}" (click)="goTo(itemChild2.link)">{{itemChild2.text}}</a>
39+
</li>
40+
</ul>
41+
</li>
42+
</ul>
43+
</li>
44+
</ul>
45+
</li>
46+
</ul>
47+
</div>
48+
`,
3249
changeDetection: ChangeDetectionStrategy.OnPush
3350
})
3451
export class ScrollSpyIndexRenderComponent implements OnInit, AfterViewInit, OnDestroy {
3552
@Input() public scrollSpyIndexRenderOptions: ScrollSpyIndexComponentOptions;
3653

37-
public stack: Array<any> = [];
38-
public parentStack: Array<any> = [];
39-
public lastItem: any;
40-
4154
public currentScrollPosition: number;
55+
public items: any[] = [];
56+
public itemsHash: any = {};
4257
public itemsToHighlight: Array<string> = [];
43-
44-
@ViewChild('container', { read: ViewContainerRef })
45-
public viewContainerRef: ViewContainerRef;
4658

4759
public defaultOptions: ScrollSpyIndexComponentOptions = {
4860
spyId: 'window',
@@ -55,9 +67,7 @@ export class ScrollSpyIndexRenderComponent implements OnInit, AfterViewInit, OnD
5567
public el: HTMLElement;
5668

5769
constructor(
58-
private compiler: Compiler,
5970
private ref: ChangeDetectorRef,
60-
private resolver: ComponentFactoryResolver,
6171
private elRef: ElementRef,
6272
private scrollSpy: ScrollSpyService,
6373
private scrollSpyIndex: ScrollSpyIndexService
@@ -103,90 +113,81 @@ export class ScrollSpyIndexRenderComponent implements OnInit, AfterViewInit, OnD
103113
}
104114

105115
update() {
106-
var items: Array<any> = this.scrollSpyIndex.getIndex(this.scrollSpyIndexRenderOptions.id) || [];
107-
var markup: string = '<ul class="nav menu">';
108-
109-
for (var i = 0; i < items.length; i++) {
110-
var item = this.itemConstruct(items[i]);
111-
112-
if (item.push) {
113-
markup += '<ul class="nav menu">';
114-
} else if (item.pop) {
115-
for (var j = 0; j < item.pop; j++) {
116-
markup += '</li></ul>';
116+
const data: Array<any> = this.scrollSpyIndex.getIndex(this.scrollSpyIndexRenderOptions.id) || [];
117+
118+
let stack: Array<any> = [];
119+
let parentStack: Array<any> = [];
120+
let lastItem: any;
121+
122+
this.items = [];
123+
this.itemsHash = {};
124+
125+
for (var i = 0; i < data.length; ++i) {
126+
// parse basic info from the dom item
127+
var item: any = {
128+
link: data[i].id,
129+
text: data[i].textContent || data[i].innerText,
130+
parents: [],
131+
children: []
132+
};
133+
134+
// build type identifier
135+
var level: string = data[i].tagName;
136+
for (var n = 0; n < data[i].classList.length; n++) {
137+
level += ',' + data[i].classList[n];
138+
}
139+
140+
// here be dragons
141+
var stacksize: number = stack.length;
142+
if (stacksize === 0) {
143+
// we are at the top level and will stay there
144+
stack.push(level);
145+
} else if (level !== stack[stacksize - 1]) {
146+
// traverse the ancestry, looking for a match
147+
for (var j = stacksize - 1; j >= 0; j--) {
148+
if (level === stack[j]) {
149+
break; // found an ancestor
150+
}
151+
}
152+
if (j < 0) {
153+
// this is a new submenu item, lets push the stack
154+
stack.push(level);
155+
parentStack.push(lastItem);
156+
} else {
157+
// we are either a sibling or higher up the tree,
158+
// lets pop the stack if needed
159+
while (stack.length > j + 1) {
160+
stack.pop();
161+
parentStack.pop();
162+
}
117163
}
118-
} else if (i !== 0) {
119-
markup += '</li>';
120164
}
121165

122-
markup += '<li [class.active]="highlight(\'' + item.link + '\')" pagemenuspy="' + item.link + '" parent="' + item.parent + '">';
123-
124-
// HACK: remove click once https://github.com/angular/angular/issues/6595 is fixed
125-
markup += '<a [routerLink]="" fragment="' + item.link + '" (click)="goTo(\'' + item.link + '\')">';
126-
markup += item.text;
127-
markup += '</a>';
128-
}
129-
markup += '</ul>';
130-
131-
this.viewContainerRef.clear();
132-
let componentFactory = this.compileToComponent(markup, () => this.getItemsToHighlight());
133-
this.viewContainerRef.createComponent(componentFactory);
134-
135-
setTimeout(() => {
136-
this.calculateHighlight();
137-
});
138-
}
139-
140-
itemConstruct(data: any) {
141-
// parse basic info from the dom item
142-
var item: any = {
143-
link: data.id,
144-
text: data.textContent || data.innerText,
145-
parent: ''
146-
};
147-
148-
// build type identifier
149-
var level: string = data.tagName;
150-
for (var i = 0; i < data.classList.length; i++) {
151-
level += ',' + data.classList[i];
152-
}
153-
154-
// here be dragons
155-
var stacksize: number = this.stack.length;
156-
if (stacksize === 0) {
157-
// we are at the top level and will stay there
158-
this.stack.push(level);
159-
} else if (level !== this.stack[stacksize - 1]) {
160-
// traverse the ancestry, looking for a match
161-
for (var j = stacksize - 1; j >= 0; j--) {
162-
if (level === this.stack[j]) {
163-
break; // found an ancestor
166+
// for next iteration
167+
lastItem = item.link;
168+
169+
// if we have a parent, lets record it
170+
if (parentStack.length > 0) {
171+
item.parents = [...parentStack];
172+
173+
let temp: any = this.items;
174+
for (var t = 0; t < parentStack.length; ++t) {
175+
if (t < parentStack.length - 1) {
176+
temp = temp.filter((e: any) => { return e.link === parentStack[t]; })[0].children;
177+
} else {
178+
temp.filter((e: any) => { return e.link === parentStack[t]; })[0].children.push(item);
179+
}
164180
}
165-
}
166-
if (j < 0) {
167-
// this is a new submenu item, lets push the this.stack
168-
this.stack.push(level);
169-
item.push = true;
170-
this.parentStack.push(this.lastItem);
171181
} else {
172-
// we are either a sibling or higher up the tree,
173-
// lets pop the this.stack if needed
174-
item.pop = stacksize - 1 - j;
175-
while (this.stack.length > j + 1) {
176-
this.stack.pop();
177-
this.parentStack.pop();
178-
}
182+
this.items.push(item);
179183
}
180-
}
181184

182-
// if we have a parent, lets record it
183-
if (this.parentStack.length > 0) {
184-
item.parent = this.parentStack[this.parentStack.length - 1];
185+
this.itemsHash[item.link] = item;
185186
}
186187

187-
// for next iteration
188-
this.lastItem = item.link;
189-
return item;
188+
setTimeout(() => {
189+
this.calculateHighlight();
190+
});
190191
}
191192

192193
calculateHighlight() {
@@ -208,56 +209,19 @@ export class ScrollSpyIndexRenderComponent implements OnInit, AfterViewInit, OnD
208209
if (!highlightItem) {
209210
highlightItem = items[0].id;
210211
}
211-
this.itemsToHighlight.push(highlightItem);
212-
213-
while (!!highlightItem) {
214-
var item = this.el.querySelector('[pagemenuspy=' + highlightItem + ']');
215-
if (!!item) {
216-
var parent = item.getAttribute('parent');
217-
if (parent) {
218-
highlightItem = parent;
219-
this.itemsToHighlight.push(highlightItem);
220-
} else {
221-
highlightItem = null;
222-
}
223-
} else {
224-
highlightItem = null;
225-
}
226-
}
212+
this.itemsToHighlight = [highlightItem, ...this.itemsHash[highlightItem].parents];
227213

228214
this.ref.markForCheck();
229215
}
230216

231-
getItemsToHighlight(): Array<string> {
232-
return this.itemsToHighlight;
217+
highlight(id: string): boolean {
218+
return this.itemsToHighlight.indexOf(id) !== -1;
233219
}
234220

235-
compileToComponent(template: string, itemsToHighlight: any): any {
236-
@Injectable()
237-
@Component({
238-
selector: 'scrollSpyMenu',
239-
template
240-
})
241-
class RenderComponent {
242-
highlight(id: string): boolean {
243-
return itemsToHighlight().indexOf(id) !== -1;
244-
}
245-
246-
// HACK: remove click once https://github.com/angular/angular/issues/6595 is fixed
247-
goTo(anchor: string) {
248-
setTimeout(() => {
249-
document.querySelector('#' + anchor).scrollIntoView();
250-
});
251-
}
252-
};
253-
254-
@NgModule({imports: [RouterModule], declarations: [RenderComponent]})
255-
class RenderModule {}
256-
257-
return this.compiler.compileModuleAndAllComponentsSync(RenderModule)
258-
.componentFactories.find((comp) =>
259-
comp.componentType === RenderComponent
260-
);
221+
goTo(anchor: string) {
222+
setTimeout(() => {
223+
document.querySelector('#' + anchor).scrollIntoView();
224+
});
261225
}
262226

263227
ngOnDestroy() {

0 commit comments

Comments
 (0)