From 6e305c5a08a8dbbba5ea2a5dc48a5afbd7073630 Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Fri, 13 Dec 2019 01:24:25 -0500 Subject: [PATCH] build: enable strictPropertyInitialization - fix related build errors - complete `this._destroyed` in `ngOnDestroy()` - replace use of deprecated `DomPortalHost` with `DomPortalOutlet` --- .../component-category-list.ts | 23 ++-- .../pages/component-list/component-list.ts | 0 .../component-sidenav/component-sidenav.ts | 49 ++++---- .../component-viewer/component-viewer.ts | 20 ++-- src/app/shared/carousel/carousel.spec.ts | 3 +- src/app/shared/carousel/carousel.ts | 110 +++++++++++------- src/app/shared/doc-viewer/doc-viewer.ts | 4 +- src/app/shared/doc-viewer/header-link.ts | 2 +- src/app/shared/example-viewer/code-snippet.ts | 4 +- .../shared/example-viewer/example-viewer.html | 4 +- .../shared/example-viewer/example-viewer.ts | 66 ++++++----- src/app/shared/ga/ga.ts | 2 +- src/app/shared/navbar/navbar.ts | 2 +- .../navigation-focus.service.ts | 6 +- .../stack-blitz/stack-blitz-button.html | 2 +- .../shared/stack-blitz/stack-blitz-button.ts | 27 +++-- src/app/shared/svg-viewer/svg-viewer.ts | 8 +- .../table-of-contents/table-of-contents.ts | 2 +- src/app/shared/theme-picker/theme-picker.ts | 2 +- tsconfig.json | 3 +- 20 files changed, 192 insertions(+), 147 deletions(-) create mode 100644 src/app/pages/component-list/component-list.ts diff --git a/src/app/pages/component-category-list/component-category-list.ts b/src/app/pages/component-category-list/component-category-list.ts index 64ed86928..5cebb8b32 100644 --- a/src/app/pages/component-category-list/component-category-list.ts +++ b/src/app/pages/component-category-list/component-category-list.ts @@ -1,13 +1,18 @@ +import {CommonModule} from '@angular/common'; import {Component, NgModule, OnDestroy, OnInit} from '@angular/core'; import {MatCardModule} from '@angular/material/card'; -import {CommonModule} from '@angular/common'; import {ActivatedRoute, Params, RouterModule} from '@angular/router'; -import {DocumentationItems, SECTIONS} from '../../shared/documentation-items/documentation-items'; -import {ComponentPageTitle} from '../page-title/page-title'; -import {SvgViewerModule} from '../../shared/svg-viewer/svg-viewer'; -import {Observable, combineLatest, Subscription} from 'rxjs'; -import {NavigationFocusModule} from '../../shared/navigation-focus/navigation-focus'; +import {combineLatest, Observable, Subscription} from 'rxjs'; +import { + DocumentationItems, + SECTIONS +} from '../../shared/documentation-items/documentation-items'; +import { + NavigationFocusModule +} from '../../shared/navigation-focus/navigation-focus'; +import {SvgViewerModule} from '../../shared/svg-viewer/svg-viewer'; +import {ComponentPageTitle} from '../page-title/page-title'; @Component({ selector: 'app-component-category-list', @@ -15,9 +20,9 @@ import {NavigationFocusModule} from '../../shared/navigation-focus/navigation-fo styleUrls: ['./component-category-list.scss'] }) export class ComponentCategoryList implements OnInit, OnDestroy { - params: Observable; - routeParamSubscription: Subscription; - _categoryListSummary: string; + params: Observable | undefined; + routeParamSubscription: Subscription = new Subscription(); + _categoryListSummary: string | undefined; constructor(public docItems: DocumentationItems, public _componentPageTitle: ComponentPageTitle, diff --git a/src/app/pages/component-list/component-list.ts b/src/app/pages/component-list/component-list.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/pages/component-sidenav/component-sidenav.ts b/src/app/pages/component-sidenav/component-sidenav.ts index 3f8a4c01d..245379caf 100644 --- a/src/app/pages/component-sidenav/component-sidenav.ts +++ b/src/app/pages/component-sidenav/component-sidenav.ts @@ -1,3 +1,8 @@ +import {animate, state, style, transition, trigger} from '@angular/animations'; +import {CdkAccordionModule} from '@angular/cdk/accordion'; +import {BreakpointObserver} from '@angular/cdk/layout'; +import {CommonModule} from '@angular/common'; +import {HttpClientModule} from '@angular/common/http'; import { Component, Input, @@ -8,22 +13,35 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; -import {DocumentationItems} from '../../shared/documentation-items/documentation-items'; +import {FormsModule} from '@angular/forms'; import {MatIconModule} from '@angular/material/icon'; +import {MatListModule} from '@angular/material/list'; import {MatSidenav, MatSidenavModule} from '@angular/material/sidenav'; +import {MatDrawerToggleResult} from '@angular/material/sidenav/drawer'; import {ActivatedRoute, Params, RouterModule, Routes} from '@angular/router'; -import {CommonModule} from '@angular/common'; -import {ComponentHeaderModule} from '../component-page-header/component-page-header'; -import {FooterModule} from '../../shared/footer/footer'; import {combineLatest, Observable, Subscription} from 'rxjs'; import {map} from 'rxjs/operators'; -import {animate, state, style, transition, trigger} from '@angular/animations'; -import {CdkAccordionModule} from '@angular/cdk/accordion'; -import {BreakpointObserver} from '@angular/cdk/layout'; + +import {DocViewerModule} from '../../shared/doc-viewer/doc-viewer-module'; +import { + DocumentationItems +} from '../../shared/documentation-items/documentation-items'; +import {FooterModule} from '../../shared/footer/footer'; +import { + NavigationFocusModule +} from '../../shared/navigation-focus/navigation-focus'; +import { + NavigationFocusService +} from '../../shared/navigation-focus/navigation-focus.service'; +import {StackBlitzButtonModule} from '../../shared/stack-blitz'; +import {SvgViewerModule} from '../../shared/svg-viewer/svg-viewer'; import { ComponentCategoryList, ComponentCategoryListModule } from '../component-category-list/component-category-list'; +import { + ComponentHeaderModule +} from '../component-page-header/component-page-header'; import { ComponentApi, ComponentExamples, @@ -31,15 +49,6 @@ import { ComponentViewer, ComponentViewerModule } from '../component-viewer/component-viewer'; -import {DocViewerModule} from '../../shared/doc-viewer/doc-viewer-module'; -import {FormsModule} from '@angular/forms'; -import {HttpClientModule} from '@angular/common/http'; -import {StackBlitzButtonModule} from '../../shared/stack-blitz'; -import {SvgViewerModule} from '../../shared/svg-viewer/svg-viewer'; -import {MatDrawerToggleResult} from '@angular/material/sidenav/drawer'; -import {MatListModule} from '@angular/material/list'; -import {NavigationFocusModule} from '../../shared/navigation-focus/navigation-focus'; -import {NavigationFocusService} from '../../shared/navigation-focus/navigation-focus.service'; // These constants are used by the ComponentSidenav for orchestrating the MatSidenav in a responsive // way. This includes hiding the sidenav, defaulting it to open, changing the mode from over to @@ -58,8 +67,8 @@ const SMALL_WIDTH_BREAKPOINT = 959; encapsulation: ViewEncapsulation.None, }) export class ComponentSidenav implements OnInit, OnDestroy { - @ViewChild(MatSidenav) sidenav: MatSidenav; - params: Observable; + @ViewChild(MatSidenav) sidenav!: MatSidenav; + params: Observable | undefined; isExtraScreenSmall: Observable; isScreenSmall: Observable; private subscriptions = new Subscription(); @@ -112,8 +121,8 @@ export class ComponentSidenav implements OnInit, OnDestroy { ], }) export class ComponentNav { - @Input() params: Observable; - currentItemId: string; + @Input() params: Observable | undefined; + currentItemId: string | undefined; constructor(public docItems: DocumentationItems) {} } diff --git a/src/app/pages/component-viewer/component-viewer.ts b/src/app/pages/component-viewer/component-viewer.ts index db2af2544..220d808a8 100644 --- a/src/app/pages/component-viewer/component-viewer.ts +++ b/src/app/pages/component-viewer/component-viewer.ts @@ -36,11 +36,9 @@ export class ComponentViewer implements OnDestroy { sections: Set = new Set(['overview', 'api']); private _destroyed = new Subject(); - constructor(_route: ActivatedRoute, - private router: Router, + constructor(_route: ActivatedRoute, private router: Router, public _componentPageTitle: ComponentPageTitle, - public docItems: DocumentationItems, - ) { + public docItems: DocumentationItems) { const routeAndParentParams = [_route.params]; if (_route.parent) { routeAndParentParams.push(_route.parent.params); @@ -78,12 +76,11 @@ export class ComponentViewer implements OnDestroy { */ @Directive() export class ComponentBaseView implements OnInit, OnDestroy { - @ViewChild('toc') tableOfContents: TableOfContents; - @ViewChildren(DocViewer) viewers: QueryList; + @ViewChild('toc') tableOfContents!: TableOfContents; + @ViewChildren(DocViewer) viewers!: QueryList; showToc: Observable; - - destroyed = new Subject(); + private _destroyed = new Subject(); constructor( public componentViewer: ComponentViewer, @@ -99,7 +96,7 @@ export class ComponentBaseView implements OnInit, OnDestroy { } ngOnInit() { - this.componentViewer.componentDocItem.pipe(takeUntil(this.destroyed)).subscribe(() => { + this.componentViewer.componentDocItem.pipe(takeUntil(this._destroyed)).subscribe(() => { if (this.tableOfContents) { this.tableOfContents.resetHeaders(); } @@ -107,7 +104,7 @@ export class ComponentBaseView implements OnInit, OnDestroy { this.showToc.pipe( skip(1), - takeUntil(this.destroyed) + takeUntil(this._destroyed) ).subscribe(() => { if (this.tableOfContents) { this.viewers.forEach(viewer => { @@ -118,7 +115,8 @@ export class ComponentBaseView implements OnInit, OnDestroy { } ngOnDestroy() { - this.destroyed.next(); + this._destroyed.next(); + this._destroyed.complete(); } updateTableOfContents(sectionName: string, docViewerContent: HTMLElement, sectionIndex = 0) { diff --git a/src/app/shared/carousel/carousel.spec.ts b/src/app/shared/carousel/carousel.spec.ts index 23426d9e6..cf7db1873 100644 --- a/src/app/shared/carousel/carousel.spec.ts +++ b/src/app/shared/carousel/carousel.spec.ts @@ -99,6 +99,5 @@ describe('HorizontalCarousel', () => { }) class CarouselTestComponent { numberOfItems = 6; - @ViewChild(Carousel) carousel: Carousel; + @ViewChild(Carousel) carousel!: Carousel; } - diff --git a/src/app/shared/carousel/carousel.ts b/src/app/shared/carousel/carousel.ts index fe84e2afb..448a088ac 100644 --- a/src/app/shared/carousel/carousel.ts +++ b/src/app/shared/carousel/carousel.ts @@ -36,20 +36,19 @@ export class CarouselItem implements FocusableOption { encapsulation: ViewEncapsulation.None }) export class Carousel implements AfterContentInit { - @Input('aria-label') ariaLabel: string; - @Input() itemWidth: number; - @ContentChildren(CarouselItem) items: QueryList; - @ViewChild('contentWrapper') wrapper: ElementRef; + @Input('aria-label') ariaLabel: string | undefined; + @Input() itemWidth: number | undefined; + @ContentChildren(CarouselItem) items!: QueryList; + @ViewChild('contentWrapper') wrapper!: ElementRef; position = 0; showPrevArrow = false; showNextArrow = true; - visibleItems: number; - shiftWidth: number; - itemsArray: CarouselItem[]; - private focusKeyManager: FocusKeyManager; + visibleItems: number | undefined; + shiftWidth: number | undefined; + itemsArray: CarouselItem[] | undefined; + private focusKeyManager: FocusKeyManager | undefined; - constructor(private readonly element: ElementRef) { - } + constructor(private readonly element: ElementRef) {} private _index = 0; @@ -59,37 +58,44 @@ export class Carousel implements AfterContentInit { set index(i: number) { this._index = i; + let lastVisibleIndex = this.items.length; + if (this.visibleItems) { + lastVisibleIndex -= this.visibleItems; + } + this.showPrevArrow = i > 0; - this.showNextArrow = i < (this.items.length - this.visibleItems); + this.showNextArrow = i < lastVisibleIndex; } onKeydown(event: KeyboardEvent) { - switch (event.key) { - case 'Tab': - if (!this.focusKeyManager.activeItem) { - this.focusKeyManager.setFirstItemActive(); + if (this.focusKeyManager != null) { + switch (event.key) { + case 'Tab': + if (!this.focusKeyManager.activeItem) { + this.focusKeyManager.setFirstItemActive(); + this._updateItemTabIndices(); + } + break; + + case 'ArrowLeft': + if (this.focusKeyManager.activeItemIndex === this.index) { + this.previous(); + } + this.focusKeyManager.setPreviousItemActive(); this._updateItemTabIndices(); - } - break; + break; - case 'ArrowLeft': - if (this.focusKeyManager.activeItemIndex === this.index) { - this.previous(); - } - this.focusKeyManager.setPreviousItemActive(); - this._updateItemTabIndices(); - break; - - case 'ArrowRight': - if (this.focusKeyManager.activeItemIndex === this.index + this.visibleItems - 1) { - this.next(); - } - this.focusKeyManager.setNextItemActive(); - this._updateItemTabIndices(); - break; + case 'ArrowRight': + if (this.focusKeyManager.activeItemIndex === this.index + (this.visibleItems || 0) - 1) { + this.next(); + } + this.focusKeyManager.setNextItemActive(); + this._updateItemTabIndices(); + break; - default: - break; + default: + break; + } } } @@ -103,7 +109,7 @@ export class Carousel implements AfterContentInit { // timeout to make sure clientWidth is defined setTimeout(() => { this.itemsArray = this.items.toArray(); - this.shiftWidth = this.items.first.element.nativeElement.clientWidth; + this.shiftWidth = this.calculateShiftWidth(this.itemsArray); this._resizeCarousel(); }); } @@ -122,35 +128,52 @@ export class Carousel implements AfterContentInit { } } + /** + * @param items array of carousel items + * @return width to shift the carousel + */ + calculateShiftWidth(items: CarouselItem[]): number { + return items[0].element.nativeElement.clientWidth; + } + private _updateItemTabIndices() { this.items.forEach((item: CarouselItem) => { - item.tabindex = item === this.focusKeyManager.activeItem ? '0' : '-1'; + if (this.focusKeyManager != null) { + item.tabindex = item === this.focusKeyManager.activeItem ? '0' : '-1'; + } }); } private _shiftItems(shiftIndex: number) { this.index += shiftIndex; - this.position += shiftIndex * this.shiftWidth; + this.position += shiftIndex * + (this.shiftWidth || this.calculateShiftWidth(this.items.toArray())); this.items.forEach((item: CarouselItem) => { item.element.nativeElement.style.transform = `translateX(-${this.position}px)`; }); } private _resizeCarousel() { + if (this.shiftWidth == null) { + this.shiftWidth = this.calculateShiftWidth(this.items.toArray()); + } const newVisibleItems = Math.max(1, Math.min( Math.floor((this.element.nativeElement.offsetWidth) / this.shiftWidth), this.items.length)); if (this.visibleItems !== newVisibleItems) { - if (this.visibleItems < newVisibleItems) { - const shiftIndex = this.index - (this.items.length - this.visibleItems) + 1; + if ((this.visibleItems || 0) < newVisibleItems) { + const lastVisibleIndex = this.items.length - (this.visibleItems || 0); + const shiftIndex = this.index - (lastVisibleIndex) + 1; if (shiftIndex > 0) { this._shiftItems(-shiftIndex); } } else { - if (this.focusKeyManager.activeItemIndex && this.focusKeyManager.activeItemIndex > - this.index + newVisibleItems - 1) { - this.focusKeyManager.setPreviousItemActive(); - this._updateItemTabIndices(); + if (this.focusKeyManager != null) { + if (this.focusKeyManager.activeItemIndex && this.focusKeyManager.activeItemIndex > + this.index + newVisibleItems - 1) { + this.focusKeyManager.setPreviousItemActive(); + this._updateItemTabIndices(); + } } } this.visibleItems = newVisibleItems; @@ -159,4 +182,3 @@ export class Carousel implements AfterContentInit { this.wrapper.nativeElement.style.width = `${this.visibleItems * this.shiftWidth}px`; } } - diff --git a/src/app/shared/doc-viewer/doc-viewer.ts b/src/app/shared/doc-viewer/doc-viewer.ts index 407d287c9..fafbee7b8 100644 --- a/src/app/shared/doc-viewer/doc-viewer.ts +++ b/src/app/shared/doc-viewer/doc-viewer.ts @@ -26,9 +26,9 @@ import {HeaderLink} from './header-link'; }) export class DocViewer implements OnDestroy { private _portalHosts: DomPortalOutlet[] = []; - private _documentFetchSubscription: Subscription; + private _documentFetchSubscription: Subscription = new Subscription(); - @Input() name: string; + @Input() name: string | undefined; /** The URL of the document to display. */ @Input() diff --git a/src/app/shared/doc-viewer/header-link.ts b/src/app/shared/doc-viewer/header-link.ts index ed1721fb0..5d1568cd8 100644 --- a/src/app/shared/doc-viewer/header-link.ts +++ b/src/app/shared/doc-viewer/header-link.ts @@ -29,7 +29,7 @@ export class HeaderLink { * Id of the anchor element. Note that is uses "example" because we instantiate the * header link components through the ComponentPortal. */ - @Input() example: string; + @Input() example: string | undefined; /** Base URL that is used to build an absolute fragment URL. */ private _baseUrl: string; diff --git a/src/app/shared/example-viewer/code-snippet.ts b/src/app/shared/example-viewer/code-snippet.ts index 0b8d86695..65b2940d2 100644 --- a/src/app/shared/example-viewer/code-snippet.ts +++ b/src/app/shared/example-viewer/code-snippet.ts @@ -13,6 +13,6 @@ import {DocViewer} from '../doc-viewer/doc-viewer'; changeDetection: ChangeDetectionStrategy.OnPush }) export class CodeSnippet { - @Input() source: string; - @ViewChild('viewer') viewer: DocViewer; + @Input() source: string | undefined; + @ViewChild('viewer') viewer!: DocViewer; } diff --git a/src/app/shared/example-viewer/example-viewer.html b/src/app/shared/example-viewer/example-viewer.html index eaebd3b86..fefd462d3 100644 --- a/src/app/shared/example-viewer/example-viewer.html +++ b/src/app/shared/example-viewer/example-viewer.html @@ -33,7 +33,7 @@
- diff --git a/src/app/shared/stack-blitz/stack-blitz-button.ts b/src/app/shared/stack-blitz/stack-blitz-button.ts index ae70fb3cb..f420e4054 100644 --- a/src/app/shared/stack-blitz/stack-blitz-button.ts +++ b/src/app/shared/stack-blitz/stack-blitz-button.ts @@ -18,25 +18,28 @@ export class StackBlitzButton { * StackBlitz not yet being ready for people with poor network connections or slow devices. */ isDisabled = false; - stackBlitzForm: HTMLFormElement; - exampleData: ExampleData; + stackBlitzForm: HTMLFormElement | undefined; + exampleData: ExampleData | undefined; @HostListener('mouseover') onMouseOver() { this.isDisabled = !this.stackBlitzForm; } @Input() - set example(example: string) { - this.exampleData = new ExampleData(example); - + set example(example: string | undefined) { if (example) { - this.stackBlitzWriter.constructStackBlitzForm(example, - this.exampleData, - example.includes('harness')) + this.exampleData = new ExampleData(example); + if (this.exampleData) { + this.stackBlitzWriter.constructStackBlitzForm(example, + this.exampleData, + example.includes('harness')) .then((stackBlitzForm: HTMLFormElement) => { this.stackBlitzForm = stackBlitzForm; this.isDisabled = false; }); + } else { + this.isDisabled = true; + } } else { this.isDisabled = true; } @@ -49,9 +52,11 @@ export class StackBlitzButton { // to submit if it is detached from the document. See the following chromium commit for // more details: // https://chromium.googlesource.com/chromium/src/+/962c2a22ddc474255c776aefc7abeba00edc7470%5E! - document.body.appendChild(this.stackBlitzForm); - this.stackBlitzForm.submit(); - document.body.removeChild(this.stackBlitzForm); + if (this.stackBlitzForm) { + document.body.appendChild(this.stackBlitzForm); + this.stackBlitzForm.submit(); + document.body.removeChild(this.stackBlitzForm); + } } } diff --git a/src/app/shared/svg-viewer/svg-viewer.ts b/src/app/shared/svg-viewer/svg-viewer.ts index 7a0c8c84d..9c42432df 100644 --- a/src/app/shared/svg-viewer/svg-viewer.ts +++ b/src/app/shared/svg-viewer/svg-viewer.ts @@ -6,13 +6,15 @@ import {Component, ElementRef, Input, NgModule, OnInit} from '@angular/core'; template: '', }) export class SvgViewer implements OnInit { - @Input() src: string; - @Input() scaleToContainer: boolean; + @Input() src: string | undefined; + @Input() scaleToContainer: boolean | undefined; constructor(private elementRef: ElementRef, private http: HttpClient) { } ngOnInit() { - this.fetchAndInlineSvgContent(this.src); + if (this.src) { + this.fetchAndInlineSvgContent(this.src); + } } private inlineSvgContent(template: string) { diff --git a/src/app/shared/table-of-contents/table-of-contents.ts b/src/app/shared/table-of-contents/table-of-contents.ts index dc7908295..970200f54 100644 --- a/src/app/shared/table-of-contents/table-of-contents.ts +++ b/src/app/shared/table-of-contents/table-of-contents.ts @@ -35,7 +35,7 @@ interface Link { templateUrl: './table-of-contents.html' }) export class TableOfContents implements OnInit, AfterViewInit, OnDestroy { - @Input() container: string; + @Input() container: string | undefined; _linkSections: LinkSection[] = []; _links: Link[] = []; diff --git a/src/app/shared/theme-picker/theme-picker.ts b/src/app/shared/theme-picker/theme-picker.ts index c2b59b629..3c8f88660 100644 --- a/src/app/shared/theme-picker/theme-picker.ts +++ b/src/app/shared/theme-picker/theme-picker.ts @@ -28,7 +28,7 @@ import {LiveAnnouncer} from '@angular/cdk/a11y'; }) export class ThemePicker implements OnInit, OnDestroy { private _queryParamSubscription = Subscription.EMPTY; - currentTheme: DocsSiteTheme; + currentTheme: DocsSiteTheme | undefined; // The below colors need to align with the themes defined in theme-picker.scss themes: DocsSiteTheme[] = [ diff --git a/tsconfig.json b/tsconfig.json index 94af12d80..6b3bd9f8a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,8 @@ "noFallthroughCasesInSwitch": true, "strictNullChecks": true, "strictBindCallApply": true, - "strictFunctionTypes": true + "strictFunctionTypes": true, + "strictPropertyInitialization": true }, "exclude": [ "src/assets",