diff --git a/src/lib/api/core/base-adapter.spec.ts b/src/lib/api/core/base-adapter.spec.ts index c7fcdd425..6b78c1627 100644 --- a/src/lib/api/core/base-adapter.spec.ts +++ b/src/lib/api/core/base-adapter.spec.ts @@ -5,10 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {ElementRef, Renderer2} from '@angular/core'; +import {ElementRef} from '@angular/core'; import {BaseFxDirectiveAdapter} from './base-adapter'; import {expect} from '../../utils/testing/custom-matchers'; -import {MediaMonitor} from '@angular/flex-layout/media-query'; +import {MediaMonitor} from '../../media-query/media-monitor'; +import {StyleUtils} from '../../utils/style-utils'; export class MockElementRef extends ElementRef { constructor() { @@ -21,7 +22,7 @@ export class MockElementRef extends ElementRef { describe('BaseFxDirectiveAdapter class', () => { let component; beforeEach(() => { - component = new BaseFxDirectiveAdapter('', {} as MediaMonitor, new MockElementRef(), {} as Renderer2, {}); // tslint:disable-line:max-line-length + component = new BaseFxDirectiveAdapter('', {} as MediaMonitor, new MockElementRef(), {} as StyleUtils); // tslint:disable-line:max-line-length }); describe('cacheInput', () => { it('should call _cacheInputArray when source is an array', () => { diff --git a/src/lib/api/core/base-adapter.ts b/src/lib/api/core/base-adapter.ts index 82ca37698..04cff0eb8 100644 --- a/src/lib/api/core/base-adapter.ts +++ b/src/lib/api/core/base-adapter.ts @@ -5,12 +5,13 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {ElementRef, Inject, PLATFORM_ID, Renderer2} from '@angular/core'; +import {ElementRef} from '@angular/core'; import {BaseFxDirective} from './base'; import {ResponsiveActivation} from './responsive-activation'; import {MediaQuerySubscriber} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; +import {StyleUtils} from '../../utils/style-utils'; /** @@ -48,9 +49,8 @@ export class BaseFxDirectiveAdapter extends BaseFxDirective { constructor(protected _baseKey: string, // non-responsive @Input property name protected _mediaMonitor: MediaMonitor, protected _elementRef: ElementRef, - protected _renderer: Renderer2, - @Inject(PLATFORM_ID) protected _platformId: Object) { - super(_mediaMonitor, _elementRef, _renderer, _platformId); + protected _styleUtils: StyleUtils) { + super(_mediaMonitor, _elementRef, _styleUtils); } /** diff --git a/src/lib/api/core/base.ts b/src/lib/api/core/base.ts index 396403067..a5d08ac27 100644 --- a/src/lib/api/core/base.ts +++ b/src/lib/api/core/base.ts @@ -11,19 +11,12 @@ import { SimpleChanges, OnChanges, SimpleChange, - Renderer2, - Inject, - PLATFORM_ID, } from '@angular/core'; import {buildLayoutCSS} from '../../utils/layout-validator'; import { StyleDefinition, - lookupStyle, - lookupInlineStyle, - applyStyleToElement, - applyStyleToElements, - lookupAttributeValue, + StyleUtils, } from '../../utils/style-utils'; import {ResponsiveActivation, KeyOptions} from '../core/responsive-activation'; @@ -70,8 +63,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges { */ constructor(protected _mediaMonitor: MediaMonitor, protected _elementRef: ElementRef, - protected _renderer: Renderer2, - @Inject(PLATFORM_ID) protected _platformId: Object) { + protected _styleUtils: StyleUtils) { } // ********************************************* @@ -137,11 +129,12 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges { /** * Quick accessor to the current HTMLElement's `display` style - * Note: this allows use to preserve the original style + * Note: this allows us to preserve the original style * and optional restore it when the mediaQueries deactivate */ protected _getDisplayStyle(source: HTMLElement = this.nativeElement): string { - return lookupStyle(this._platformId, source || this.nativeElement, 'display'); + const query = 'display'; + return this._styleUtils.lookupStyle(source, query); } /** @@ -149,7 +142,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges { */ protected _getAttributeValue(attribute: string, source: HTMLElement = this.nativeElement): string { - return lookupAttributeValue(source || this.nativeElement, attribute); + return this._styleUtils.lookupAttributeValue(source, attribute); } /** @@ -158,15 +151,20 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges { * Check inline style first then check computed (stylesheet) style. * And optionally add the flow value to element's inline style. */ - protected _getFlowDirection(target: any, addIfMissing = false): string { + protected _getFlowDirection(target: HTMLElement, addIfMissing = false): string { let value = 'row'; + let hasInlineValue = ''; + const query = 'flex-direction'; if (target) { - value = lookupStyle(this._platformId, target, 'flex-direction') || 'row'; - let hasInlineValue = lookupInlineStyle(target, 'flex-direction'); + + value = this._styleUtils.lookupStyle(target, query) || 'row'; + hasInlineValue = this._styleUtils.lookupInlineStyle(target, query); if (!hasInlineValue && addIfMissing) { - applyStyleToElements(this._renderer, buildLayoutCSS(value), [target]); + const style = buildLayoutCSS(value); + const elements = [target]; + this._styleUtils.applyStyleToElements(style, elements); } } @@ -178,16 +176,15 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges { */ protected _applyStyleToElement(style: StyleDefinition, value?: string | number, - nativeElement: any = this.nativeElement) { - let element = nativeElement || this.nativeElement; - applyStyleToElement(this._renderer, element, style, value); + element: HTMLElement = this.nativeElement) { + this._styleUtils.applyStyleToElement(element, style, value); } /** * Applies styles given via string pair or object map to the directive's element. */ - protected _applyStyleToElements(style: StyleDefinition, elements: HTMLElement[ ]) { - applyStyleToElements(this._renderer, style, elements || []); + protected _applyStyleToElements(style: StyleDefinition, elements: HTMLElement[]) { + this._styleUtils.applyStyleToElements(style, elements); } /** diff --git a/src/lib/api/ext/class.spec.ts b/src/lib/api/ext/class.spec.ts index c1268e74d..f6d61d0b8 100644 --- a/src/lib/api/ext/class.spec.ts +++ b/src/lib/api/ext/class.spec.ts @@ -22,6 +22,8 @@ import {BreakPointRegistry} from '../../media-query/breakpoints/break-point-regi import {ClassDirective} from './class'; import {MediaQueriesModule} from '../../media-query/_module'; +import {ServerStylesheet} from '../../utils/server-stylesheet'; +import {StyleUtils} from '../../utils/style-utils'; describe('class directive', () => { let fixture: ComponentFixture; @@ -47,7 +49,9 @@ describe('class directive', () => { declarations: [TestClassComponent, ClassDirective], providers: [ BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia} + {provide: MatchMedia, useClass: MockMatchMedia}, + ServerStylesheet, + StyleUtils, ] }); }); diff --git a/src/lib/api/ext/class.ts b/src/lib/api/ext/class.ts index c07636a14..1da35fc7b 100644 --- a/src/lib/api/ext/class.ts +++ b/src/lib/api/ext/class.ts @@ -19,8 +19,6 @@ import { SimpleChanges, Self, OnInit, - Inject, - PLATFORM_ID, } from '@angular/core'; import {NgClass} from '@angular/common'; @@ -29,6 +27,7 @@ import {BaseFxDirectiveAdapter} from '../core/base-adapter'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {RendererAdapter} from '../core/renderer-adapter'; +import {StyleUtils} from '../../utils/style-utils'; /** NgClass allowed inputs **/ export type NgClassType = string | string[] | Set | {[klass: string]: any}; @@ -95,8 +94,8 @@ export class ClassDirective extends BaseFxDirective protected _ngEl: ElementRef, protected _renderer: Renderer2, @Optional() @Self() private _ngClassInstance: NgClass, - @Inject(PLATFORM_ID) protected _platformId: Object) { - super(monitor, _ngEl, _renderer, _platformId); + protected _styleUtils: StyleUtils) { + super(monitor, _ngEl, _styleUtils); this._configureAdapters(); } @@ -139,7 +138,10 @@ export class ClassDirective extends BaseFxDirective */ protected _configureAdapters() { this._base = new BaseFxDirectiveAdapter( - 'ngClass', this.monitor, this._ngEl, this._renderer, this._platformId + 'ngClass', + this.monitor, + this._ngEl, + this._styleUtils ); if (!this._ngClassInstance) { // Create an instance NgClass Directive instance only if `ngClass=""` has NOT been defined on diff --git a/src/lib/api/ext/hide.spec.ts b/src/lib/api/ext/hide.spec.ts index 44ad28ef1..7c4bd8f4d 100644 --- a/src/lib/api/ext/hide.spec.ts +++ b/src/lib/api/ext/hide.spec.ts @@ -23,6 +23,8 @@ import { } from '../../utils/testing/helpers'; import {ShowHideDirective} from './show-hide'; import {MediaQueriesModule} from '../../media-query/_module'; +import {ServerStylesheet} from '../../utils/server-stylesheet'; +import {StyleUtils} from '../../utils/style-utils'; describe('hide directive', () => { let fixture: ComponentFixture; @@ -60,7 +62,9 @@ describe('hide directive', () => { declarations: [TestHideComponent, ShowHideDirective], providers: [ BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia} + {provide: MatchMedia, useClass: MockMatchMedia}, + ServerStylesheet, + StyleUtils, ] }); }); diff --git a/src/lib/api/ext/img-src.ts b/src/lib/api/ext/img-src.ts index b821bb3b4..d682313d7 100644 --- a/src/lib/api/ext/img-src.ts +++ b/src/lib/api/ext/img-src.ts @@ -12,12 +12,11 @@ import { OnInit, OnChanges, Renderer2, - Inject, - PLATFORM_ID, } from '@angular/core'; import {BaseFxDirective} from '../core/base'; import {MediaMonitor} from '../../media-query/media-monitor'; +import {StyleUtils} from '../../utils/style-utils'; /** * This directive provides a responsive API for the HTML 'src' attribute @@ -57,12 +56,12 @@ export class ImgSrcDirective extends BaseFxDirective implements OnInit, OnChange @Input('src.gt-lg') set srcGtLg(val) { this._cacheInput('srcGtLg', val); } /* tslint:enable */ - constructor(elRef: ElementRef, - renderer: Renderer2, - monitor: MediaMonitor, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); - this._cacheInput('src', elRef.nativeElement.getAttribute('src') || ''); + constructor(protected _elRef: ElementRef, + protected _renderer: Renderer2, + protected _monitor: MediaMonitor, + protected _styleUtils: StyleUtils) { + super(_monitor, _elRef, _styleUtils); + this._cacheInput('src', _elRef.nativeElement.getAttribute('src') || ''); } /** diff --git a/src/lib/api/ext/show-hide.ts b/src/lib/api/ext/show-hide.ts index ca9ea65f9..65e38a216 100644 --- a/src/lib/api/ext/show-hide.ts +++ b/src/lib/api/ext/show-hide.ts @@ -12,12 +12,9 @@ import { OnInit, OnChanges, OnDestroy, - Renderer2, SimpleChanges, Self, Optional, - Inject, - PLATFORM_ID, } from '@angular/core'; import {Subscription} from 'rxjs/Subscription'; @@ -26,6 +23,7 @@ import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {LayoutDirective} from '../flexbox/layout'; +import {StyleUtils} from '../../utils/style-utils'; const FALSY = ['false', false, 0]; @@ -104,19 +102,18 @@ export class ShowHideDirective extends BaseFxDirective implements OnInit, OnChan * */ constructor(monitor: MediaMonitor, - @Optional() @Self() protected _layout: LayoutDirective, + @Optional() @Self() protected layout: LayoutDirective, protected elRef: ElementRef, - protected renderer: Renderer2, - @Inject(PLATFORM_ID) protected platformId: Object) { + protected styleUtils: StyleUtils) { - super(monitor, elRef, renderer, platformId); + super(monitor, elRef, styleUtils); - if (_layout) { + if (layout) { /** * The Layout can set the display:flex (and incorrectly affect the Hide/Show directives. * Whenever Layout [on the same element] resets its CSS, then update the Hide/Show CSS */ - this._layoutWatcher = _layout.layout$.subscribe(() => this._updateWithValue()); + this._layoutWatcher = layout.layout$.subscribe(() => this._updateWithValue()); } } @@ -130,7 +127,7 @@ export class ShowHideDirective extends BaseFxDirective implements OnInit, OnChan * unless it was already explicitly specified inline or in a CSS stylesheet. */ protected _getDisplayStyle(): string { - return this._layout ? 'flex' : super._getDisplayStyle(); + return this.layout ? 'flex' : super._getDisplayStyle(); } diff --git a/src/lib/api/ext/show.spec.ts b/src/lib/api/ext/show.spec.ts index 01162d8bc..6beac8047 100644 --- a/src/lib/api/ext/show.spec.ts +++ b/src/lib/api/ext/show.spec.ts @@ -18,6 +18,8 @@ import {FlexLayoutModule} from '../../module'; import {customMatchers} from '../../utils/testing/custom-matchers'; import {makeCreateTestComponent, expectNativeEl} from '../../utils/testing/helpers'; +import {ServerStylesheet} from '../../utils/server-stylesheet'; +import {StyleUtils} from '../../utils/style-utils'; describe('show directive', () => { let fixture: ComponentFixture; @@ -39,7 +41,9 @@ describe('show directive', () => { declarations: [TestShowComponent], providers: [ BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia} + {provide: MatchMedia, useClass: MockMatchMedia}, + ServerStylesheet, + StyleUtils, ] }); }); diff --git a/src/lib/api/ext/style.spec.ts b/src/lib/api/ext/style.spec.ts index 421a4f017..b801f5438 100644 --- a/src/lib/api/ext/style.spec.ts +++ b/src/lib/api/ext/style.spec.ts @@ -22,6 +22,8 @@ import {customMatchers} from '../../utils/testing/custom-matchers'; import { makeCreateTestComponent, expectNativeEl } from '../../utils/testing/helpers'; +import {ServerStylesheet} from '../../utils/server-stylesheet'; +import {StyleUtils} from '../../utils/style-utils'; describe('style directive', () => { let fixture: ComponentFixture; @@ -43,7 +45,9 @@ describe('style directive', () => { declarations: [TestStyleComponent, LayoutDirective, StyleDirective], providers: [ BreakPointRegistry, DEFAULT_BREAKPOINTS_PROVIDER, - {provide: MatchMedia, useClass: MockMatchMedia} + {provide: MatchMedia, useClass: MockMatchMedia}, + ServerStylesheet, + StyleUtils, ] }); }); diff --git a/src/lib/api/ext/style.ts b/src/lib/api/ext/style.ts index e9c36dd23..ef792304f 100644 --- a/src/lib/api/ext/style.ts +++ b/src/lib/api/ext/style.ts @@ -19,8 +19,6 @@ import { Self, SimpleChanges, OnInit, - Inject, - PLATFORM_ID, } from '@angular/core'; import {NgStyle} from '@angular/common'; @@ -38,6 +36,7 @@ import { ngStyleUtils as _ } from '../../utils/style-transforms'; import {RendererAdapter} from '../core/renderer-adapter'; +import {StyleUtils} from '../../utils/style-utils'; /** @@ -93,9 +92,9 @@ export class StyleDirective extends BaseFxDirective protected _renderer: Renderer2, protected _differs: KeyValueDiffers, @Optional() @Self() private _ngStyleInstance: NgStyle, - @Inject(PLATFORM_ID) protected _platformId: Object) { + protected _styleUtils: StyleUtils) { - super(monitor, _ngEl, _renderer, _platformId); + super(monitor, _ngEl, _styleUtils); this._configureAdapters(); } @@ -138,9 +137,12 @@ export class StyleDirective extends BaseFxDirective */ protected _configureAdapters() { this._base = new BaseFxDirectiveAdapter( - 'ngStyle', this.monitor, this._ngEl, this._renderer, this._platformId + 'ngStyle', + this.monitor, + this._ngEl, + this._styleUtils ); - if ( !this._ngStyleInstance ) { + if (!this._ngStyleInstance) { // Create an instance NgClass Directive instance only if `ngClass=""` has NOT been // defined on the same host element; since the responsive variations may be defined... let adapter = new RendererAdapter(this._renderer); diff --git a/src/lib/api/flexbox/flex-align.ts b/src/lib/api/flexbox/flex-align.ts index ff4f30e69..96efccfa6 100644 --- a/src/lib/api/flexbox/flex-align.ts +++ b/src/lib/api/flexbox/flex-align.ts @@ -12,15 +12,13 @@ import { OnInit, OnChanges, OnDestroy, - Renderer2, SimpleChanges, - Inject, - PLATFORM_ID, } from '@angular/core'; import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; +import {StyleUtils} from '../../utils/style-utils'; /** * 'flex-align' flexbox styling directive @@ -58,9 +56,8 @@ export class FlexAlignDirective extends BaseFxDirective implements OnInit, OnCha /* tslint:enable */ constructor(monitor: MediaMonitor, elRef: ElementRef, - renderer: Renderer2, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); + styleUtils: StyleUtils) { + super(monitor, elRef, styleUtils); } diff --git a/src/lib/api/flexbox/flex-fill.ts b/src/lib/api/flexbox/flex-fill.ts index 8e73334df..038212be6 100644 --- a/src/lib/api/flexbox/flex-fill.ts +++ b/src/lib/api/flexbox/flex-fill.ts @@ -5,10 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {Directive, ElementRef, Inject, PLATFORM_ID, Renderer2} from '@angular/core'; +import {Directive, ElementRef} from '@angular/core'; import {MediaMonitor} from '../../media-query/media-monitor'; import {BaseFxDirective} from '../core/base'; +import {StyleUtils} from '../../utils/style-utils'; const FLEX_FILL_CSS = { 'margin': 0, @@ -31,9 +32,8 @@ const FLEX_FILL_CSS = { export class FlexFillDirective extends BaseFxDirective { constructor(monitor: MediaMonitor, public elRef: ElementRef, - public renderer: Renderer2, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); + styleUtils: StyleUtils) { + super(monitor, elRef, styleUtils); this._applyStyleToElement(FLEX_FILL_CSS); } } diff --git a/src/lib/api/flexbox/flex-offset.ts b/src/lib/api/flexbox/flex-offset.ts index a97f8992e..e46186346 100644 --- a/src/lib/api/flexbox/flex-offset.ts +++ b/src/lib/api/flexbox/flex-offset.ts @@ -13,11 +13,8 @@ import { OnChanges, OnDestroy, Optional, - Renderer2, SimpleChanges, SkipSelf, - Inject, - PLATFORM_ID, } from '@angular/core'; import {Subscription} from 'rxjs/Subscription'; @@ -27,6 +24,7 @@ import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {LayoutDirective} from './layout'; import {isFlowHorizontal} from '../../utils/layout-validator'; +import {StyleUtils} from '../../utils/style-utils'; /** * 'flex-offset' flexbox styling directive @@ -61,10 +59,9 @@ export class FlexOffsetDirective extends BaseFxDirective implements OnInit, OnCh /* tslint:enable */ constructor(monitor: MediaMonitor, elRef: ElementRef, - renderer: Renderer2, @Optional() @SkipSelf() protected _container: LayoutDirective, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); + styleUtils: StyleUtils) { + super(monitor, elRef, styleUtils); this.watchParentFlow(); diff --git a/src/lib/api/flexbox/flex-order.ts b/src/lib/api/flexbox/flex-order.ts index 8c3953554..cc834f524 100644 --- a/src/lib/api/flexbox/flex-order.ts +++ b/src/lib/api/flexbox/flex-order.ts @@ -12,15 +12,13 @@ import { OnInit, OnChanges, OnDestroy, - Renderer2, SimpleChanges, - Inject, - PLATFORM_ID, } from '@angular/core'; import {BaseFxDirective} from '../core/base'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; +import {StyleUtils} from '../../utils/style-utils'; /** * 'flex-order' flexbox styling directive @@ -56,9 +54,8 @@ export class FlexOrderDirective extends BaseFxDirective implements OnInit, OnCha /* tslint:enable */ constructor(monitor: MediaMonitor, elRef: ElementRef, - renderer: Renderer2, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); + styleUtils: StyleUtils) { + super(monitor, elRef, styleUtils); } // ********************************************* diff --git a/src/lib/api/flexbox/flex.ts b/src/lib/api/flexbox/flex.ts index a502a0bf2..83875d744 100644 --- a/src/lib/api/flexbox/flex.ts +++ b/src/lib/api/flexbox/flex.ts @@ -8,14 +8,11 @@ import { Directive, ElementRef, - Inject, Input, OnChanges, OnDestroy, OnInit, Optional, - PLATFORM_ID, - Renderer2, SimpleChanges, SkipSelf, } from '@angular/core'; @@ -29,6 +26,7 @@ import {MediaMonitor} from '../../media-query/media-monitor'; import {LayoutDirective} from './layout'; import {validateBasis} from '../../utils/basis-validator'; import {isFlowHorizontal} from '../../utils/layout-validator'; +import {StyleUtils} from '../../utils/style-utils'; /** Built-in aliases for different flex-basis values. */ @@ -85,11 +83,10 @@ export class FlexDirective extends BaseFxDirective implements OnInit, OnChanges, // for the parent flex container for this flex item. constructor(monitor: MediaMonitor, elRef: ElementRef, - renderer: Renderer2, @Optional() @SkipSelf() protected _container: LayoutDirective, - @Inject(PLATFORM_ID) platformId: Object) { + protected styleUtils: StyleUtils) { - super(monitor, elRef, renderer, platformId); + super(monitor, elRef, styleUtils); this._cacheInput('flex', ''); this._cacheInput('shrink', 1); diff --git a/src/lib/api/flexbox/layout-align.ts b/src/lib/api/flexbox/layout-align.ts index cd18aacc0..d47a34626 100644 --- a/src/lib/api/flexbox/layout-align.ts +++ b/src/lib/api/flexbox/layout-align.ts @@ -13,11 +13,8 @@ import { OnDestroy, OnInit, Optional, - Renderer2, SimpleChanges, Self, - Inject, - PLATFORM_ID, } from '@angular/core'; import {Subscription} from 'rxjs/Subscription'; import {extendObject} from '../../utils/object-extend'; @@ -28,6 +25,7 @@ import {MediaMonitor} from '../../media-query/media-monitor'; import {LayoutDirective} from './layout'; import {LAYOUT_VALUES, isFlowHorizontal} from '../../utils/layout-validator'; +import {StyleUtils} from '../../utils/style-utils'; /** * 'layout-align' flexbox styling directive @@ -70,10 +68,10 @@ export class LayoutAlignDirective extends BaseFxDirective implements OnInit, OnC /* tslint:enable */ constructor( monitor: MediaMonitor, - elRef: ElementRef, renderer: Renderer2, + elRef: ElementRef, @Optional() @Self() container: LayoutDirective, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); + styleUtils: StyleUtils) { + super(monitor, elRef, styleUtils); if (container) { // Subscribe to layout direction changes this._layoutWatcher = container.layout$.subscribe(this._onLayoutChange.bind(this)); diff --git a/src/lib/api/flexbox/layout-gap.ts b/src/lib/api/flexbox/layout-gap.ts index 7626ebe58..35fe56d71 100644 --- a/src/lib/api/flexbox/layout-gap.ts +++ b/src/lib/api/flexbox/layout-gap.ts @@ -10,15 +10,12 @@ import { ElementRef, Input, OnChanges, - Renderer2, SimpleChanges, Self, AfterContentInit, Optional, OnDestroy, NgZone, - Inject, - PLATFORM_ID, } from '@angular/core'; import {Subscription} from 'rxjs/Subscription'; @@ -27,6 +24,7 @@ import {LayoutDirective} from './layout'; import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {LAYOUT_VALUES} from '../../utils/layout-validator'; +import {StyleUtils} from '../../utils/style-utils'; /** * 'layout-padding' styling directive @@ -67,11 +65,10 @@ export class LayoutGapDirective extends BaseFxDirective implements AfterContentI /* tslint:enable */ constructor(monitor: MediaMonitor, elRef: ElementRef, - renderer: Renderer2, @Optional() @Self() container: LayoutDirective, private _zone: NgZone, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); + styleUtils: StyleUtils) { + super(monitor, elRef, styleUtils); if (container) { // Subscribe to layout direction changes this._layoutWatcher = container.layout$.subscribe(this._onLayoutChange.bind(this)); diff --git a/src/lib/api/flexbox/layout.ts b/src/lib/api/flexbox/layout.ts index 62d6d5ae5..4f865c50d 100644 --- a/src/lib/api/flexbox/layout.ts +++ b/src/lib/api/flexbox/layout.ts @@ -12,10 +12,7 @@ import { OnInit, OnChanges, OnDestroy, - Renderer2, SimpleChanges, - Inject, - PLATFORM_ID, } from '@angular/core'; import {Observable} from 'rxjs/Observable'; @@ -24,6 +21,7 @@ import {MediaChange} from '../../media-query/media-change'; import {MediaMonitor} from '../../media-query/media-monitor'; import {buildLayoutCSS} from '../../utils/layout-validator'; import {ReplaySubject} from 'rxjs/ReplaySubject'; +import {StyleUtils} from '../../utils/style-utils'; /** * 'layout' flexbox styling directive * Defines the positioning flow direction for the child elements: row or column @@ -75,9 +73,8 @@ export class LayoutDirective extends BaseFxDirective implements OnInit, OnChange */ constructor(monitor: MediaMonitor, elRef: ElementRef, - renderer: Renderer2, - @Inject(PLATFORM_ID) platformId: Object) { - super(monitor, elRef, renderer, platformId); + styleUtils: StyleUtils) { + super(monitor, elRef, styleUtils); this._announcer = new ReplaySubject(1); this.layout$ = this._announcer.asObservable(); } diff --git a/src/lib/media-query/match-media.ts b/src/lib/media-query/match-media.ts index 14378a55c..128675ed7 100644 --- a/src/lib/media-query/match-media.ts +++ b/src/lib/media-query/match-media.ts @@ -20,27 +20,76 @@ import {Observable} from 'rxjs/Observable'; import {filter} from 'rxjs/operators/filter'; import {MediaChange} from './media-change'; +import {BreakPoint} from '../media-query/breakpoints/break-point'; /** - * EventHandler callback with the mediaQuery [range] activates or deactivates + * Special server-only class to simulate a MediaQueryList and + * - supports manual activation to simulate mediaQuery matching + * - manages listeners */ -export interface MediaQueryListListener { - // Function with Window's MediaQueryList argument - (mql: MediaQueryList): void; -} +export class ServerMediaQueryList implements MediaQueryList { + private _isActive = false; + private _listeners: Array = []; -/** - * EventDispatcher for a specific mediaQuery [range] - */ -export interface MediaQueryList { - readonly matches: boolean; - readonly media: string; + get matches(): boolean { + return this._isActive; + } - addListener(listener: MediaQueryListListener): void; + get media(): string { + return this._mediaQuery; + } - removeListener(listener: MediaQueryListListener): void; -} + constructor(private _mediaQuery: string) { } + + /** + * + */ + destroy() { + this.deactivate(); + this._listeners = []; + } + /** + * Notify all listeners that 'matches === TRUE' + */ + activate(): ServerMediaQueryList { + if (!this._isActive) { + this._isActive = true; + this._listeners.forEach((callback) => { + callback(this); + }); + } + return this; + } + + /** + * Notify all listeners that 'matches === false' + */ + deactivate(): ServerMediaQueryList { + if (this._isActive) { + this._isActive = false; + this._listeners.forEach((callback) => { + callback(this); + }); + } + return this; + } + + /** + * + */ + addListener(listener: MediaQueryListListener) { + if (this._listeners.indexOf(listener) === -1) { + this._listeners.push(listener); + } + if (this._isActive) { + listener(this); + } + } + + removeListener(_: MediaQueryListListener) { + } +} /** * MediaMonitor configures listeners to mediaQuery changes and publishes an Observable facade to @@ -51,7 +100,7 @@ export interface MediaQueryList { */ @Injectable() export class MatchMedia { - protected _registry: Map; + protected _registry: Map; protected _source: BehaviorSubject; protected _observable$: Observable; @@ -59,11 +108,29 @@ export class MatchMedia { protected _rendererFactory: RendererFactory2, @Inject(DOCUMENT) protected _document: any, @Inject(PLATFORM_ID) protected _platformId: Object) { - this._registry = new Map(); + this._registry = new Map(); this._source = new BehaviorSubject(new MediaChange(true)); this._observable$ = this._source.asObservable(); } + /** + * Activate the specified breakpoint if we're on the server, no-op otherwise + */ + activateBreakpoint(bp: BreakPoint) { + if (!isPlatformBrowser(this._platformId)) { + (this._registry.get(bp.mediaQuery) as ServerMediaQueryList).activate(); + } + } + + /** + * Deactivate the specified breakpoint if we're on the server, no-op otherwise + */ + deactivateBreakpoint(bp: BreakPoint) { + if (!isPlatformBrowser(this._platformId)) { + (this._registry.get(bp.mediaQuery) as ServerMediaQueryList).deactivate(); + } + } + /** * For the specified mediaQuery? */ @@ -104,7 +171,7 @@ export class MatchMedia { list.forEach(query => { let mql = this._registry.get(query); - let onMQLEvent = (e: MediaQueryList) => { + let onMQLEvent = (e: MediaQueryList|ServerMediaQueryList) => { this._zone.run(() => { let change = new MediaChange(e.matches, query); this._source.next(change); @@ -128,18 +195,11 @@ export class MatchMedia { * Call window.matchMedia() to build a MediaQueryList; which * supports 0..n listeners for activation/deactivation */ - protected _buildMQL(query: string): MediaQueryList { + protected _buildMQL(query: string): MediaQueryList|ServerMediaQueryList { let canListen = isPlatformBrowser(this._platformId) && !!(window).matchMedia('all').addListener; - return canListen ? (window).matchMedia(query) : { - matches: query === 'all' || query === '', - media: query, - addListener: () => { - }, - removeListener: () => { - } - }; + return canListen ? (window).matchMedia(query) : new ServerMediaQueryList(query); } /** diff --git a/src/lib/module.ts b/src/lib/module.ts index c0a0f87e1..98dbca40e 100644 --- a/src/lib/module.ts +++ b/src/lib/module.ts @@ -31,6 +31,9 @@ import {ShowHideDirective} from './api/ext/show-hide'; import {ClassDirective} from './api/ext/class'; import {StyleDirective} from './api/ext/style'; import {ImgSrcDirective} from './api/ext/img-src'; +import {ServerStylesheet} from './utils/server-stylesheet'; +import {SERVER_PROVIDER} from './utils/server-provider'; +import {StyleUtils} from './utils/style-utils'; /** * Since the equivalent results are easily achieved with a css class attached to each @@ -65,7 +68,10 @@ const ALL_DIRECTIVES = [ providers: [ MEDIA_MONITOR_PROVIDER, DEFAULT_BREAKPOINTS_PROVIDER, // Extend defaults with internal custom breakpoints - OBSERVABLE_MEDIA_PROVIDER + OBSERVABLE_MEDIA_PROVIDER, + ServerStylesheet, + StyleUtils, + SERVER_PROVIDER, ] }) export class FlexLayoutModule { diff --git a/src/lib/utils/server-provider.ts b/src/lib/utils/server-provider.ts new file mode 100644 index 000000000..bdf6345b4 --- /dev/null +++ b/src/lib/utils/server-provider.ts @@ -0,0 +1,146 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { + APP_BOOTSTRAP_LISTENER, + PLATFORM_ID, + RendererFactory2, + RendererType2, + InjectionToken, // tslint:disable-line:no-unused-variable + ComponentRef, // tslint:disable-line:no-unused-variable + ViewEncapsulation, + Renderer2, +} from '@angular/core'; +import {DOCUMENT, isPlatformBrowser} from '@angular/common'; +import {ServerStylesheet} from './server-stylesheet'; +import {BREAKPOINTS} from '../media-query/breakpoints/break-points-token'; +import {BreakPoint} from '../media-query/breakpoints/break-point'; +import {MatchMedia} from '../media-query/match-media'; + +const CLASS_NAME = 'flex-layout-'; +let UNIQUE_CLASS = 0; + +/** + * create @media queries based on a virtual stylesheet + * * Adds a unique class to each element and stores it + * in a shared classMap for later reuse + */ +function formatStyle(stylesheet: Map>, + renderer: Renderer2, + mediaQuery: string, + classMap: Map) { + let styleText = ` + @media ${mediaQuery} {`; + stylesheet.forEach((styles, el) => { + let className = classMap.get(el); + if (!className) { + className = `${CLASS_NAME}${UNIQUE_CLASS++}`; + classMap.set(el, className); + } + renderer.addClass(el, className); + styleText += ` + .${className} {`; + styles.forEach((v, k) => { + if (v) { + styleText += ` + ${k}: ${v};`; + } + }); + styleText += ` + }`; + }); + styleText += ` + }\n`; + + return styleText; +} + +/** + * format the static @media queries for all breakpoints + * to be used on the server and append them to the + */ +function serverStyles(renderer: Renderer2, + serverSheet: ServerStylesheet, + breakpoints: BreakPoint[], + matchMedia: MatchMedia, + _document: any) { + const styleTag = renderer.createElement('style'); + const classMap = new Map(); + const defaultStyles = new Map(serverSheet.stylesheet); + let styleText = formatStyle(defaultStyles, renderer, 'all', classMap); + + breakpoints.reverse(); + breakpoints.forEach((bp, i) => { + serverSheet.clearStyles(); + + if (i > 0) { + matchMedia.deactivateBreakpoint(breakpoints[i - 1]); + } + + matchMedia.activateBreakpoint(bp); + const stylesheet = new Map(serverSheet.stylesheet); + if (stylesheet.size > 0) { + styleText += formatStyle(stylesheet, renderer, bp.mediaQuery, classMap); + } + }); + + renderer.addClass(styleTag, `${CLASS_NAME}ssr`); + renderer.setValue(styleTag, styleText); + renderer.appendChild(_document.head, styleTag); +} + +/** + * Add or remove static styles depending on the current + * platform + */ +export function addStyles(serverSheet: ServerStylesheet, + matchMedia: MatchMedia, + _document: Document, + rendererFactory: RendererFactory2, + platformId: Object, + breakpoints: BreakPoint[]) { + // necessary because of angular/angular/issues/14485 + const res = () => { + const renderType: RendererType2 = { + id: '-1', + encapsulation: ViewEncapsulation.None, + styles: [], + data: {} + }; + const renderer = rendererFactory.createRenderer(_document, renderType); + if (!isPlatformBrowser(platformId)) { + serverStyles(renderer, serverSheet, breakpoints, matchMedia, _document); + } else { + const elements = Array.from(_document.querySelectorAll(`[class*=${CLASS_NAME}]`)); + const classRegex = new RegExp(/\bflex-layout-.+?\b/, 'g'); + elements.forEach(el => { + el.classList.contains(`${CLASS_NAME}ssr`) ? + el.remove() : el.className.replace(classRegex, ''); + }); + } + }; + + return res; +} + +/** + * Provider to set static styles on the server and remove + * them on the browser + */ +export const SERVER_PROVIDER = { + provide: APP_BOOTSTRAP_LISTENER, + useFactory: addStyles, + deps: [ + ServerStylesheet, + MatchMedia, + DOCUMENT, + RendererFactory2, + PLATFORM_ID, + BREAKPOINTS, + ], + multi: true +}; diff --git a/src/lib/utils/server-stylesheet.ts b/src/lib/utils/server-stylesheet.ts new file mode 100644 index 000000000..7f168fee3 --- /dev/null +++ b/src/lib/utils/server-stylesheet.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {Injectable} from '@angular/core'; +import {StyleDefinition} from './style-utils'; +import {applyCssPrefixes} from './auto-prefixer'; + +@Injectable() +export class ServerStylesheet { + + readonly stylesheet = new Map>(); + + constructor() { } + + addStyleToElement(element: any, style: StyleDefinition, value?: string | number) { + let styles = {}; + if (typeof style === 'string') { + styles[style] = value; + style = styles; + } + + styles = applyCssPrefixes(style); + this._applyMultiValueStyleToElement(styles, element); + } + + addStyleToElements(style: StyleDefinition, elements: HTMLElement[]) { + const styles = applyCssPrefixes(style); + elements.forEach(el => { + this._applyMultiValueStyleToElement(styles, el); + }); + } + + clearStyles() { + this.stylesheet.clear(); + } + + getStyleForElement(el: HTMLElement, styleName: string): string { + const styles = this.stylesheet.get(el); + return styles ? (styles.get(styleName) || '') : ''; + } + + private _applyMultiValueStyleToElement(styles: {}, element: any) { + Object.keys(styles).sort().forEach(key => { + const values = Array.isArray(styles[key]) ? styles[key] : [styles[key]]; + values.sort(); + for (let value of values) { + const stylesheet = this.stylesheet.get(element); + if (stylesheet) { + stylesheet.set(key, value); + } else { + this.stylesheet.set(element, new Map([[key, value]])); + } + } + }); + } +} diff --git a/src/lib/utils/style-utils.ts b/src/lib/utils/style-utils.ts index 37049296b..fb7bb4c4f 100644 --- a/src/lib/utils/style-utils.ts +++ b/src/lib/utils/style-utils.ts @@ -5,9 +5,86 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {Renderer2} from '@angular/core'; +import { + Inject, Injectable, PLATFORM_ID, Renderer2, RendererFactory2, RendererType2, + ViewEncapsulation +} from '@angular/core'; +import {DOCUMENT, isPlatformBrowser} from '@angular/common'; + import {applyCssPrefixes} from './auto-prefixer'; -import {isPlatformBrowser} from '@angular/common'; +import {ServerStylesheet} from '../utils/server-stylesheet'; + +@Injectable() +export class StyleUtils { + + private _renderer: Renderer2; + + constructor(private _serverStylesheet: ServerStylesheet, + private _rendererFactory: RendererFactory2, + @Inject(DOCUMENT) private _document, + @Inject(PLATFORM_ID) private _platformId) { + this._renderer = _rendererFactory.createRenderer(_document, RENDERER_TYPE); + } + + /** + * Applies styles given via string pair or object map to the directive element. + */ + applyStyleToElement(element: HTMLElement, style: StyleDefinition, value?: string | number) { + if (isPlatformBrowser(this._platformId)) { + applyStyleToElement(this._renderer, element, style, value); + } else { + this._serverStylesheet.addStyleToElement(element, style, value); + } + } + + /** + * Applies styles given via string pair or object map to the directive's element. + */ + applyStyleToElements(style: StyleDefinition, elements: HTMLElement[]) { + if (isPlatformBrowser(this._platformId)) { + applyStyleToElements(this._renderer, style, elements || []); + } else { + this._serverStylesheet.addStyleToElements(style, elements || []); + } + } + + /** + * Find the DOM element's raw attribute value (if any) + */ + lookupAttributeValue(element: HTMLElement, attribute: string): string { + return element.getAttribute(attribute) || ''; + } + + /** + * Find the DOM element's inline style value (if any) + */ + lookupInlineStyle(element: HTMLElement, styleName: string): string { + return element.style[styleName]; + } + + /** + * Determine the inline or inherited CSS style + * @TODO(CaerusKaru): platform-server has no implementation for getComputedStyle + */ + lookupStyle(element: HTMLElement, styleName: string, inlineOnly = false): string { + if (isPlatformBrowser(this._platformId)) { + let value = ''; + if (element) { + let immediateValue = value = this.lookupInlineStyle(element, styleName); + if (!inlineOnly) { + value = immediateValue || (isPlatformBrowser(this._platformId) && + getComputedStyle(element).getPropertyValue(styleName)) || ''; + } + } + + // Note: 'inline' is the default of all elements, unless UA stylesheet overrides; + // in which case getComputedStyle() should determine a valid value. + return value ? value.trim() : 'block'; + } else { + return this._serverStylesheet.getStyleForElement(element, styleName); + } + } +} /** * Definition of a css style. Either a property name (e.g. "flex-basis") or an object @@ -19,7 +96,7 @@ export type StyleDefinition = string | { [property: string]: string | number | n /** * Applies styles given via string pair or object map to the directive element. */ -export function applyStyleToElement(renderer: Renderer2, +function applyStyleToElement(renderer: Renderer2, element: any, style: StyleDefinition, value?: string | number) { @@ -37,10 +114,10 @@ export function applyStyleToElement(renderer: Renderer2, /** * Applies styles given via string pair or object map to the directive's element. */ -export function applyStyleToElements(renderer: Renderer2, +function applyStyleToElements(renderer: Renderer2, style: StyleDefinition, - elements: HTMLElement[ ]) { - let styles = applyCssPrefixes(style); + elements: HTMLElement[]) { + const styles = applyCssPrefixes(style); elements.forEach(el => { applyMultiValueStyleToElement(styles, el, renderer); @@ -52,7 +129,7 @@ export function applyStyleToElements(renderer: Renderer2, * Each value will be added as element style. * Keys are sorted to add prefixed styles (like -webkit-x) first, before the standard ones. */ -export function applyMultiValueStyleToElement(styles: {}, element: any, renderer: Renderer2) { +function applyMultiValueStyleToElement(styles: {}, element: any, renderer: Renderer2) { Object.keys(styles).sort().forEach(key => { const values = Array.isArray(styles[key]) ? styles[key] : [styles[key]]; values.sort(); @@ -62,38 +139,10 @@ export function applyMultiValueStyleToElement(styles: {}, element: any, renderer }); } -/** - * Find the DOM element's raw attribute value (if any) - */ -export function lookupAttributeValue(element: HTMLElement, attribute: string): string { - return element.getAttribute(attribute) || ''; -} -/** - * Find the DOM element's inline style value (if any) - */ -export function lookupInlineStyle(element: HTMLElement, styleName: string): string { - return element.style[styleName]; -} - -/** - * Determine the inline or inherited CSS style - * @TODO(CaerusKaru): platform-server has no implementation for getComputedStyle - */ -export function lookupStyle(_platformId: Object, - element: HTMLElement, - styleName: string, - inlineOnly = false): string { - let value = ''; - if (element) { - let immediateValue = value = lookupInlineStyle(element, styleName); - if (!inlineOnly) { - value = immediateValue || (isPlatformBrowser(_platformId) && - getComputedStyle(element).getPropertyValue(styleName)) || ''; - } - } - - // Note: 'inline' is the default of all elements, unless UA stylesheet overrides; - // in which case getComputedStyle() should determine a valid value. - return value ? value.trim() : 'block'; -} +const RENDERER_TYPE: RendererType2 = { + id: '-1', + encapsulation: ViewEncapsulation.None, + styles: [], + data: {} +}; diff --git a/src/universal-app/app/responsive-app.ts b/src/universal-app/app/responsive-app.ts index 044d43809..edabe0867 100644 --- a/src/universal-app/app/responsive-app.ts +++ b/src/universal-app/app/responsive-app.ts @@ -11,7 +11,7 @@ import {SplitModule} from './splitter/split.module'; styleUrls: ['./responsive-app.css'], template: `
-
+
Column #1 - Row #1
    @@ -23,7 +23,7 @@ import {SplitModule} from './splitter/split.module';
    -
    +
    Column #2 - Row #1