Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

fix(api): defer getComputedStyle() calls until ngOnInit phase #374

Merged
merged 1 commit into from
Aug 14, 2017

Conversation

ThomasBurleson
Copy link
Contributor

@ThomasBurleson ThomasBurleson commented Aug 10, 2017

Previously, the Show/Hide directives would query the display style during directive instantiation.

For elements instantiated during *ngFor, constructor calls to getComputedStyle() will incorrectly return “” due to failures in queries for styles in CSS stylesheets or inline styles. Deferring these types of queries to the ngOnInit() lifecycle event, enables valid response to queries for both inline styles and stylesheets.

Fixes #310.

@ThomasBurleson
Copy link
Contributor Author

@crisbeto - would you mind reviewing this ?

Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it feasible to have a unit test for this?

*/
ngOnInit() {
this._display = this._getDisplayStyle();
this._hasInitialized = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasInitialized is only used in a ngDoCheck hook. According to this chart, it shouldn't be possible for ngDoCheck to fire before ngOnInit, making the flag redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean. Many of the directives using ngOnInit(), the hasInitialized() is a custom method used in the Flex directives.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that the only place I see it being used is https://github.com/angular/flex-layout/pull/374/files#diff-5bc48b81f8309cb16bb22da0a329e466R140 which shouldn't be possible to fire before ngOnInit. I might be missing some context here, though.

Copy link
Contributor Author

@ThomasBurleson ThomasBurleson Aug 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct that ShowHideDirective::ngOnChanges() uses this.hasInitialized.

ngOnChanges() is called 1x before ngOnInit() and then later when the @input values are externally changed. The intention of that logic is to SKIP any responses to changes until after ngOnInit().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, makes sense. I suppose it also depends on the component tree.

@@ -127,7 +135,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
let value = this._lookupStyle(element, 'display');

// Note: 'inline' is the default of all elements, unless UA stylesheet overrides
return value ? value.trim() : 'inline';
return value ? value.trim() : ((element.nodeType === 1) ? 'block' : 'inline');
Copy link
Member

@crisbeto crisbeto Aug 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge fan of nested ternaries. Consider breaking it out into an if and a ternary? E.g.

if (value) {
  return value;
}

return element.nodeType === 1 ? 'block' : 'inline';

@@ -127,7 +135,7 @@ export abstract class BaseFxDirective implements OnDestroy, OnChanges {
let value = this._lookupStyle(element, 'display');

// Note: 'inline' is the default of all elements, unless UA stylesheet overrides
return value ? value.trim() : 'inline';
return value ? value.trim() : ((element.nodeType === 1) ? 'block' : 'inline');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that checking that the nodeType === 1 is a valid way to verify that the element is block-level. 1 only means that it's an Element node which is true for almost everything except cases like comments or raw text nodes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. That is true. Any suggestions?

Perhaps, we should KISS with return value ? value.trim() : 'block' ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't be true necessarily for all cases. One potential solution can be to have a list of the default block-level elements and verify it against the element.nodeName, assuming that everything else is inline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does not seem reasonable for this library.

Copy link
Contributor Author

@ThomasBurleson ThomasBurleson Aug 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_lookupStyle() checks in-line styles or calls getComputedStyle(). So the expected response should usually be defined.

'inline' is the default of all elements, unless UA stylesheet overrides; which case getComputedStyle() should determine a valid value.

I think return value ? value.trim() : 'inline' is a reasonable fallback when _lookupStyle() fails.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will catch most cases, but not the ones that are set to block by the UA, which are:

p
h1, h2, h3, h4, h5, h6
ol, ul
pre
address
blockquote
dl
div
fieldset
form
hr
noscript
table

@ThomasBurleson
Copy link
Contributor Author

@crisbeto - working on 1-2 unit tests.

@ThomasBurleson ThomasBurleson force-pushed the thomas/issue-310 branch 2 times, most recently from 2c549a3 to 430069e Compare August 11, 2017 12:41
@ThomasBurleson
Copy link
Contributor Author

@crisbeto - unit-tests provided. Can you review last bits?

Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LGTM, a few more nits.

* found in the LICENSE file at https://angular.io/license
*/
import {Renderer2} from '@angular/core';
import {ɵgetDOM as getDom} from '@angular/platform-browser';
Copy link
Member

@crisbeto crisbeto Aug 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intended (ɵgetDOM)? It looks like something generated by the AoT compiler that shouldn't be consumed directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is intended. Same as in Angular.

export function lookupStyle(element: HTMLElement, styleName: string, inlineOnly = false): any {
let value = '';
if (element) {
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK try/catch has a slight performance overhead. This one could be worked around by checking whether typeof getComputedStyle !== 'undefined'.

/**
* Determine the inline or inherited CSS style
*/
export function lookupStyle(element: HTMLElement, styleName: string, inlineOnly = false): any {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value for this one is a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

styleName: string,
styleValue: string = null,
inlineOnly = true): boolean {
let searchComputed = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is only used once, it seems redundant to define a local function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

@ThomasBurleson ThomasBurleson added pr: lgtm This PR has been approved by the reviewer pr: needs presubmit labels Aug 11, 2017
Copy link
Member

@crisbeto crisbeto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ThomasBurleson
Copy link
Contributor Author

@crisbeto - Thank you sir for your help and reviews.

Previously, the Show/Hide directives would query the `display` style during directive instantiation.

For elements instantiated during `*ngFor`, constructor calls to getComputedStyle() will incorrectly return “” due to failures in queries for styles in CSS stylesheets or inline styles. Deferring these types of queries to the ngOnInit() lifecycle event, enables valid response to queries for both inline styles and stylesheets.

Fixes #310.
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes pr: lgtm This PR has been approved by the reviewer pr: needs presubmit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

fxShow is setting display:inline; in IE11 and Edge
4 participants