From 940fbf796cd4434350fa38aa64b30184c4e74ff8 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 8 Mar 2019 22:19:36 +0100 Subject: [PATCH] fix(ivy): unable to inject class and style attributes (#29192) Fixes not being able to inject the `class` and `style` attributes via the `Attribute` decorator. This PR resolves FW-1139. PR Close #29192 --- packages/core/src/render3/di.ts | 39 +++++++++++++++++++----- packages/core/test/acceptance/di_spec.ts | 29 +++++++++++++++++- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 639ebe8b20..dcfb0669cf 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -17,7 +17,7 @@ import {getComponentDef, getDirectiveDef, getPipeDef} from './definition'; import {NG_ELEMENT_ID} from './fields'; import {DirectiveDef} from './interfaces/definition'; import {NO_PARENT_INJECTOR, NodeInjectorFactory, PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags, TNODE, isFactory} from './interfaces/injector'; -import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node'; +import {AttributeMarker, TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType} from './interfaces/node'; import {DECLARATION_VIEW, INJECTOR, LView, TData, TVIEW, TView, T_HOST} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert'; import {getLView, getPreviousOrParentTNode, setTNodeAndViewData} from './state'; @@ -271,14 +271,39 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str ngDevMode && assertDefined(tNode, 'expecting tNode'); const attrs = tNode.attrs; if (attrs) { - for (let i = 0; i < attrs.length; i = i + 2) { - const attrName = attrs[i]; + const attrsLength = attrs.length; + let i = 0; + while (i < attrsLength) { + const value = attrs[i]; + // If we hit a `Bindings` or `Template` marker then we are done. - if (isNameOnlyAttributeMarker(attrName)) break; - // TODO(FW-1137): Skip namespaced attributes - // TODO(FW-1139): supports classes/styles in @Attribute injection - if (attrName == attrNameToInject) { + if (isNameOnlyAttributeMarker(value)) break; + + if (typeof value === 'number') { + // Skip to the first value of the marked attribute. + i++; + if (value === AttributeMarker.Classes && attrNameToInject === 'class') { + let accumulatedClasses = ''; + while (i < attrsLength && typeof attrs[i] === 'string') { + accumulatedClasses += ' ' + attrs[i++]; + } + return accumulatedClasses.trim(); + } else if (value === AttributeMarker.Styles && attrNameToInject === 'style') { + let accumulatedStyles = ''; + while (i < attrsLength && typeof attrs[i] === 'string') { + accumulatedStyles += `${attrs[i++]}: ${attrs[i++]}; `; + } + return accumulatedStyles.trim(); + } else { + while (i < attrsLength && typeof attrs[i] === 'string') { + i++; + } + } + } else if (value === attrNameToInject) { + // TODO(FW-1137): Skip namespaced attributes return attrs[i + 1] as string; + } else { + i = i + 2; } } } diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index 108e598b13..b7bd4b134e 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {ChangeDetectorRef, Component, Directive, Inject, LOCALE_ID, Optional, Pipe, PipeTransform, SkipSelf, ViewChild} from '@angular/core'; +import {Attribute, ChangeDetectorRef, Component, Directive, Inject, LOCALE_ID, Optional, Pipe, PipeTransform, SkipSelf, ViewChild} from '@angular/core'; import {ViewRef} from '@angular/core/src/render3/view_ref'; import {TestBed} from '@angular/core/testing'; @@ -130,4 +130,31 @@ describe('di', () => { fixture.detectChanges(); expect(fixture.componentInstance.myDir.localeId).toBe('en-GB'); }); + + it('should be able to inject different kinds of attributes', () => { + @Directive({selector: '[dir]'}) + class MyDir { + constructor( + @Attribute('class') public className: string, + @Attribute('style') public inlineStyles: string, + @Attribute('other-attr') public otherAttr: string) {} + } + @Component({ + template: + '
' + }) + class MyComp { + @ViewChild(MyDir) directiveInstance !: MyDir; + } + + TestBed.configureTestingModule({declarations: [MyDir, MyComp, MyComp]}); + const fixture = TestBed.createComponent(MyComp); + fixture.detectChanges(); + + const directive = fixture.componentInstance.directiveInstance; + + expect(directive.otherAttr).toBe('value'); + expect(directive.className).toBe('hello there'); + expect(directive.inlineStyles).toBe('margin: 1px; color: red;'); + }); });