diff --git a/aio/content/examples/pipes/src/app/app.module.ts b/aio/content/examples/pipes/src/app/app.module.ts index f1d75597fcb6f5..6e864c440f6694 100644 --- a/aio/content/examples/pipes/src/app/app.module.ts +++ b/aio/content/examples/pipes/src/app/app.module.ts @@ -1,26 +1,23 @@ // #docregion +import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; +import { ExponentialStrengthPipe } from './exponential-strength.pipe'; +import { FetchJsonPipe } from './fetch-json.pipe'; import { FlyingHeroesComponent, FlyingHeroesImpureComponent } from './flying-heroes.component'; +import { FlyingHeroesImpurePipe, FlyingHeroesPipe } from './flying-heroes.pipe'; import { HeroAsyncMessageComponent } from './hero-async-message.component'; import { HeroBirthdayComponent } from './hero-birthday1.component'; import { HeroBirthday2Component } from './hero-birthday2.component'; import { HeroListComponent } from './hero-list.component'; -import { PowerBoosterComponent } from './power-booster.component'; import { PowerBoostCalculatorComponent } from './power-boost-calculator.component'; -import { - FlyingHeroesPipe, - FlyingHeroesImpurePipe -} from './flying-heroes.pipe'; -import { FetchJsonPipe } from './fetch-json.pipe'; -import { ExponentialStrengthPipe } from './exponential-strength.pipe'; +import { PowerBoosterComponent } from './power-booster.component'; @NgModule({ imports: [ @@ -43,6 +40,6 @@ import { ExponentialStrengthPipe } from './exponential-strength.pipe'; FetchJsonPipe, ExponentialStrengthPipe ], - bootstrap: [ AppComponent ] + bootstrap: [AppComponent] }) -export class AppModule { } +export class AppModule {} diff --git a/aio/content/examples/pipes/src/app/fetch-json.pipe.ts b/aio/content/examples/pipes/src/app/fetch-json.pipe.ts index 9fcdf341e151fe..4141e19a11441e 100644 --- a/aio/content/examples/pipes/src/app/fetch-json.pipe.ts +++ b/aio/content/examples/pipes/src/app/fetch-json.pipe.ts @@ -1,23 +1,24 @@ // #docregion +import { HttpClient } from '@angular/common/http'; import { Pipe, PipeTransform } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; + // #docregion pipe-metadata @Pipe({ name: 'fetch', pure: false }) // #enddocregion pipe-metadata -export class FetchJsonPipe implements PipeTransform { +export class FetchJsonPipe implements PipeTransform { private cachedData: any = null; private cachedUrl = ''; - constructor(private http: HttpClient) { } + constructor(private http: HttpClient) {} transform(url: string): any { if (url !== this.cachedUrl) { this.cachedData = null; this.cachedUrl = url; - this.http.get(url).subscribe( result => this.cachedData = result ); + this.http.get(url).subscribe(result => (this.cachedData = result)); } return this.cachedData; diff --git a/aio/content/guide/architecture-components.md b/aio/content/guide/architecture-components.md index 7b8e808f352ed8..f0f2be93b685c9 100644 --- a/aio/content/guide/architecture-components.md +++ b/aio/content/guide/architecture-components.md @@ -40,7 +40,7 @@ Angular inserts an instance of the `HeroListComponent` view between those tags. * `templateUrl`: The module-relative address of this component's HTML template. Alternatively, you can provide the HTML template inline, as the value of the `template` property. This template defines the component's _host view_. -* `providers`: An array of **dependency injection providers** for services that the component requires. In the example, this tells Angular how to provide the `HeroService` instance that the component's constructor uses to get the list of heroes to display. +* `providers`: An array of **dependency injection providers** for services that the component requires. In the example, this tells Angular how provide the `HeroService` instance that the component's constructor uses to get the list of heroes to display.
diff --git a/modules/benchmarks/src/tree/render3/tree.ts b/modules/benchmarks/src/tree/render3/tree.ts index 3fc03b2f81a3c6..43d33291f3845e 100644 --- a/modules/benchmarks/src/tree/render3/tree.ts +++ b/modules/benchmarks/src/tree/render3/tree.ts @@ -44,8 +44,8 @@ export class TreeComponent { s(c0); { T(1); } e(); - C(2); C(3); + C(4); } if (rf & RenderFlags.Update) { sp(0, 0, ctx.data.depth % 2 ? '' : 'grey'); @@ -67,7 +67,7 @@ export class TreeComponent { } } cr(); - cR(3); + cR(4); { if (ctx.data.right != null) { let rf0 = V(0); @@ -117,8 +117,8 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) { s(c1); { T(2); } e(); - C(3); C(4); + C(5); } e(); } @@ -134,7 +134,7 @@ export function TreeTpl(rf: RenderFlags, ctx: TreeNode) { } } cr(); - cR(4); + cR(5); { if (ctx.right != null) { let rf0 = V(0); diff --git a/packages/common/src/i18n/format_date.ts b/packages/common/src/i18n/format_date.ts index 54c41ecf0eb727..031b50e56e3bac 100644 --- a/packages/common/src/i18n/format_date.ts +++ b/packages/common/src/i18n/format_date.ts @@ -198,6 +198,22 @@ function formatFractionalSeconds(milliseconds: number, digits: number): string { return strMs.substr(0, digits); } +/** + * Trim a fractional part to `digits` number of digits. + * Right pads with "0" to fit the requested number of digits if needed. + * + * @param num The fractional part value + * @param digits The width of the output + */ +function trimRPadFractional(num: number, digits: number): string { + let strNum = String(num); + // Add padding at the end + while (strNum.length < digits) { + strNum = strNum + 0; + } + return strNum.substr(0, digits); +} + /** * Returns a date formatter that transforms a date into its locale digit representation */ diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index a226cbb4997023..872203b3d416fe 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -15,22 +15,21 @@ import {assertDefined, assertEqual, assertLessThan, assertNotDefined, assertNotE import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors'; import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} from './hooks'; import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; -import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; -import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view'; +import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, CurrentMatchesList, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {StylingContext, StylingIndex, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling'; -import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util'; +import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyles as renderElementStyles, updateStyleMap as updateElementStyleMap, updateStyleProp as updateElementStyleProp} from './styling'; +import {isDifferent, stringify} from './util'; import {ViewRef} from './view_ref'; - /** * Directive (D) sets a property on all component instances using this constant as a key and the * component's host node (LElement) as the value. This is used in methods like detectChanges to @@ -1340,6 +1339,62 @@ function getStylingContext(index: number): StylingContext { return stylingContext; } +/** + * Assign any inline style values to the element during creation mode. + * + * This instruction is meant to be called during creation mode to apply all styling + * (e.g. `style="..."`) values to the element. This is also where the provided index + * value is allocated for the styling details for its corresponding element (the element + * index is the previous index value from this one). + * + * (Note this function calls `elementStylingApply` immediately when called.) + * + * + * @param index Index value which will be allocated to store styling data for the element. + * (Note that this is not the element index, but rather an index value allocated + * specifically for element styling--the index must be the next index after the element + * index.) + * @param styles A key/value map of CSS styles that will be registered on the element. + * Each individual style will be used on the element as long as it is not overridden + * by any styles placed on the element by multiple (`[style]`) or singular (`[style.prop]`) + * bindings. If a style binding changes its value to null then the initial styling + * values that are passed in here will be applied to the element (if matched). + */ +export function elementStyling(index: number, styles?: (string | number)[] | null): void { + const tNode = load(index - 1).tNode; + if (!tNode.stylingTemplate) { + // initialize the styling template. + tNode.stylingTemplate = createStylingContextTemplate(styles); + } + // Allocate space but leave null for lazy creation. + viewData[index + HEADER_OFFSET] = null; + if (styles && styles.length) { + elementStylingApply(index); + } +} + +/** + * Retrieve the `StylingContext` at a given index. + * + * This method lazily creates the `StylingContext`. This is because in most cases + * we have styling without any bindings. Creating `StylingContext` eagerly would mean that + * every style declaration such as `
` would result `StyleContext` + * which would create unnecessary memory pressure. + * + * @param index Index of the style allocation. See: `elementStyling`. + */ +function getStylingContext(index: number): StylingContext { + let stylingContext = load(index); + if (!stylingContext) { + const lElement: LElementNode = load(index - 1); + const tNode = lElement.tNode; + ngDevMode && + assertDefined(tNode.stylingTemplate, 'getStylingContext() called before elementStyling()'); + stylingContext = viewData[index + HEADER_OFFSET] = allocStylingContext(tNode.stylingTemplate !); + } + return stylingContext; +} + /** * Apply all styling values to the element which have been queued by any styling instructions. * @@ -1355,7 +1410,7 @@ function getStylingContext(index: number): StylingContext { * index.) */ export function elementStylingApply(index: number): void { - renderElementStyles(getStylingContext(index), renderer); + renderElementStyles(load(index - 1), getStylingContext(index), renderer); } /** @@ -1412,17 +1467,12 @@ export function elementStyleProp( * (Note that this is not the element index, but rather an index value allocated * specifically for element styling--the index must be the next index after the element * index.) - * @param styles A key/value style map of the styles that will be applied to the given element. - * Any missing styles (that have already been applied to the element beforehand) will be - * removed (unset) from the element's styling. - * @param classes A key/value style map of CSS classes that will be added to the given element. - * Any missing classes (that have already been applied to the element beforehand) will be - * removed (unset) from the element's list of CSS classes. - */ -export function elementStylingMap( - index: number, styles: {[styleName: string]: any} | null, - classes?: {[key: string]: any} | string | null): void { - updateStylingMap(getStylingContext(index), styles, classes); + * @param value A value indicating if a given style should be added or removed. + * The expected shape of `value` is an object where keys are style names and the values + * are their corresponding values to set. If value is null, then the style is removed. + */ +export function elementStyle(index: number, value: {[styleName: string]: any} | null): void { + updateElementStyleMap(getStylingContext(index), value); } ////////////////////////// diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index 2e3f74a8123e67..7e5a5fa0f80784 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -10,7 +10,7 @@ import {NgForOfContext} from '@angular/common'; import {RenderFlags, directiveInject} from '../../src/render3'; import {defineComponent} from '../../src/render3/definition'; -import {bind, container, element, elementAttribute, elementEnd, elementProperty, elementStart, elementStyleProp, elementStyling, elementStylingApply, elementStylingMap, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, element, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleProp, elementStyling, elementStylingApply, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions'; import {InitialStylingFlags} from '../../src/render3/interfaces/definition'; import {AttributeMarker, LElementNode, LNode} from '../../src/render3/interfaces/node'; import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer'; @@ -23,13 +23,13 @@ import {ComponentFixture, TemplateFixture} from './render_util'; describe('instructions', () => { function createAnchor() { elementStart(0, 'a'); - elementStyling(); + elementStyling(1); elementEnd(); } function createDiv(initialStyles?: (string | number)[]) { elementStart(0, 'div'); - elementStyling(initialStyles && Array.isArray(initialStyles) ? initialStyles : null); + elementStyling(1, initialStyles && Array.isArray(initialStyles) ? initialStyles : null); elementEnd(); } @@ -193,15 +193,15 @@ describe('instructions', () => { it('should use sanitizer function', () => { const t = new TemplateFixture(() => { return createDiv(['background-image']); }); t.update(() => { - elementStyleProp(0, 0, 'url("http://server")', sanitizeStyle); - elementStylingApply(0); + elementStyleProp(1, 0, 'url("http://server")', sanitizeStyle); + elementStylingApply(1); }); // nothing is set because sanitizer suppresses it. expect(t.html).toEqual('
'); t.update(() => { - elementStyleProp(0, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle); - elementStylingApply(0); + elementStyleProp(1, 0, bypassSanitizationTrustStyle('url("http://server")'), sanitizeStyle); + elementStylingApply(1); }); expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image')) .toEqual('url("http://server")'); @@ -211,15 +211,15 @@ describe('instructions', () => { describe('elementStyleMap', () => { function createDivWithStyle() { elementStart(0, 'div'); - elementStyling(['height', InitialStylingFlags.VALUES_MODE, 'height', '10px']); + elementStyling(1, ['height', InitialStylingFlags.INITIAL_STYLES, 'height', '10px']); elementEnd(); } it('should add style', () => { const fixture = new TemplateFixture(createDivWithStyle); fixture.update(() => { - elementStylingMap(0, {'background-color': 'red'}); - elementStylingApply(0); + elementStyle(1, {'background-color': 'red'}); + elementStylingApply(1); }); expect(fixture.html).toEqual('
'); });