From f114d6c560a00d0d5d8a529c2b0f03a6a8696df4 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Sat, 30 Apr 2016 16:13:03 -0700 Subject: [PATCH] fix(compiler): fix cross view references and providers with `useValue`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, we would create all fields in the generated views with visibility `private`. This does not work if an embedded view references a directive / element in a parent view. In Dart, this was no problem so far as it does not have a `private` modifier. Before, `useValue` in a provider did not work when doing offline compile, as so far the `MetadataResolver` was only used for jit mode. Now, `useValue` supports any kind of value that the static reflector can return. E.g. primitives, arrays, string maps, … Closes #8366 --- .../src/compiler/metadata_resolver.ts | 22 ++++++++-- .../src/compiler/output/dart_emitter.ts | 3 ++ .../src/compiler/output/js_emitter.ts | 4 ++ .../src/compiler/output/ts_emitter.ts | 3 ++ modules/angular2/src/compiler/util.ts | 44 ++++++++++++++++++- .../compiler/view_compiler/compile_element.ts | 41 +++++++++++++---- .../compiler/view_compiler/compile_pipe.ts | 3 +- .../compiler/view_compiler/compile_query.ts | 3 +- .../compiler/view_compiler/event_binder.ts | 2 + .../compiler/view_compiler/property_binder.ts | 1 + .../src/compiler/view_compiler/util.ts | 2 +- .../compiler/view_compiler/view_builder.ts | 11 ++--- modules/angular2/src/facade/lang.dart | 1 + modules/angular2/src/facade/lang.ts | 5 +++ .../linker/view_injector_integration_spec.ts | 25 +++++++++++ tools/compiler_cli/test/src/basic.ts | 11 ++--- tools/compiler_cli/test/src/features.ts | 29 ++++++++++++ 17 files changed, 176 insertions(+), 34 deletions(-) create mode 100644 tools/compiler_cli/test/src/features.ts diff --git a/modules/angular2/src/compiler/metadata_resolver.ts b/modules/angular2/src/compiler/metadata_resolver.ts index 2ba43c7bea..9e086296c5 100644 --- a/modules/angular2/src/compiler/metadata_resolver.ts +++ b/modules/angular2/src/compiler/metadata_resolver.ts @@ -24,7 +24,7 @@ import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/metadata import {reflector} from 'angular2/src/core/reflection/reflection'; import {Injectable, Inject, Optional} from 'angular2/src/core/di'; import {PLATFORM_DIRECTIVES, PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes'; -import {MODULE_SUFFIX, sanitizeIdentifier} from './util'; +import {MODULE_SUFFIX, sanitizeIdentifier, ValueTransformer, visitValue} from './util'; import {assertArrayOfStrings} from './assertions'; import {getUrlScheme} from 'angular2/src/compiler/url_resolver'; import {Provider} from 'angular2/src/core/di/provider'; @@ -314,9 +314,7 @@ export class CompileMetadataResolver { isPresent(provider.useClass) ? this.getTypeMetadata(provider.useClass, staticTypeModuleUrl(provider.useClass)) : null, - useValue: isPresent(provider.useValue) ? - new cpl.CompileIdentifierMetadata({runtime: provider.useValue}) : - null, + useValue: convertToCompileValue(provider.useValue), useFactory: isPresent(provider.useFactory) ? this.getFactoryMetadata(provider.useFactory, staticTypeModuleUrl(provider.useFactory)) : @@ -417,3 +415,19 @@ function calcTemplateBaseUrl(reflector: ReflectorReader, type: any, return reflector.importUri(type); } + +// Only fill CompileIdentifierMetadata.runtime if needed... +function convertToCompileValue(value: any): any { + return visitValue(value, new _CompileValueConverter(), null); +} + +class _CompileValueConverter extends ValueTransformer { + visitOther(value: any, context: any): any { + if (isStaticType(value)) { + return new cpl.CompileIdentifierMetadata( + {name: value['name'], moduleUrl: staticTypeModuleUrl(value)}); + } else { + return new cpl.CompileIdentifierMetadata({runtime: value}); + } + } +} diff --git a/modules/angular2/src/compiler/output/dart_emitter.ts b/modules/angular2/src/compiler/output/dart_emitter.ts index 6abb8918cf..6af8b729bc 100644 --- a/modules/angular2/src/compiler/output/dart_emitter.ts +++ b/modules/angular2/src/compiler/output/dart_emitter.ts @@ -344,6 +344,9 @@ class _DartEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisito private _visitIdentifier(value: CompileIdentifierMetadata, typeParams: o.Type[], ctx: EmitterVisitorContext): void { + if (isBlank(value.name)) { + throw new BaseException(`Internal error: unknown identifier ${value}`); + } if (isPresent(value.moduleUrl) && value.moduleUrl != this._moduleUrl) { var prefix = this.importsWithPrefixes.get(value.moduleUrl); if (isBlank(prefix)) { diff --git a/modules/angular2/src/compiler/output/js_emitter.ts b/modules/angular2/src/compiler/output/js_emitter.ts index 1646551e3d..06a230adf6 100644 --- a/modules/angular2/src/compiler/output/js_emitter.ts +++ b/modules/angular2/src/compiler/output/js_emitter.ts @@ -7,6 +7,7 @@ import { RegExpWrapper, StringWrapper } from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; import {OutputEmitter, EmitterVisitorContext} from './abstract_emitter'; import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; import {getImportModulePath, ImportEnv} from './path_util'; @@ -34,6 +35,9 @@ class JsEmitterVisitor extends AbstractJsEmitterVisitor { constructor(private _moduleUrl: string) { super(); } visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any { + if (isBlank(ast.value.name)) { + throw new BaseException(`Internal error: unknown identifier ${ast.value}`); + } if (isPresent(ast.value.moduleUrl) && ast.value.moduleUrl != this._moduleUrl) { var prefix = this.importsWithPrefixes.get(ast.value.moduleUrl); if (isBlank(prefix)) { diff --git a/modules/angular2/src/compiler/output/ts_emitter.ts b/modules/angular2/src/compiler/output/ts_emitter.ts index b7bd8b7bed..6cf1ad949c 100644 --- a/modules/angular2/src/compiler/output/ts_emitter.ts +++ b/modules/angular2/src/compiler/output/ts_emitter.ts @@ -311,6 +311,9 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor private _visitIdentifier(value: CompileIdentifierMetadata, typeParams: o.Type[], ctx: EmitterVisitorContext): void { + if (isBlank(value.name)) { + throw new BaseException(`Internal error: unknown identifier ${value}`); + } if (isPresent(value.moduleUrl) && value.moduleUrl != this._moduleUrl) { var prefix = this.importsWithPrefixes.get(value.moduleUrl); if (isBlank(prefix)) { diff --git a/modules/angular2/src/compiler/util.ts b/modules/angular2/src/compiler/util.ts index 715acc61c0..a7a6b8f584 100644 --- a/modules/angular2/src/compiler/util.ts +++ b/modules/angular2/src/compiler/util.ts @@ -1,4 +1,13 @@ -import {IS_DART, StringWrapper, Math, isBlank} from 'angular2/src/facade/lang'; +import { + IS_DART, + StringWrapper, + Math, + isBlank, + isArray, + isStrictStringMap, + isPrimitive +} from 'angular2/src/facade/lang'; +import {StringMapWrapper} from 'angular2/src/facade/collection'; export var MODULE_SUFFIX = IS_DART ? '.dart' : ''; @@ -27,3 +36,36 @@ export function splitAtColon(input: string, defaultValues: string[]): string[] { export function sanitizeIdentifier(name: string): string { return StringWrapper.replaceAll(name, /\W/g, '_'); } + +export function visitValue(value: any, visitor: ValueVisitor, context: any): any { + if (isArray(value)) { + return visitor.visitArray(value, context); + } else if (isStrictStringMap(value)) { + return visitor.visitStringMap(<{[key: string]: any}>value, context); + } else if (isBlank(value) || isPrimitive(value)) { + return visitor.visitPrimitive(value, context); + } else { + return visitor.visitOther(value, context); + } +} + +export interface ValueVisitor { + visitArray(arr: any[], context: any): any; + visitStringMap(map: {[key: string]: any}, context: any): any; + visitPrimitive(value: any, context: any): any; + visitOther(value: any, context: any): any; +} + +export class ValueTransformer implements ValueVisitor { + visitArray(arr: any[], context: any): any { + return arr.map(value => visitValue(value, this, context)); + } + visitStringMap(map: {[key: string]: any}, context: any): any { + var result = {}; + StringMapWrapper.forEach(map, + (value, key) => { result[key] = visitValue(value, this, context); }); + return result; + } + visitPrimitive(value: any, context: any): any { return value; } + visitOther(value: any, context: any): any { return value; } +} diff --git a/modules/angular2/src/compiler/view_compiler/compile_element.ts b/modules/angular2/src/compiler/view_compiler/compile_element.ts index 0a554746de..3b642ed9b9 100644 --- a/modules/angular2/src/compiler/view_compiler/compile_element.ts +++ b/modules/angular2/src/compiler/view_compiler/compile_element.ts @@ -1,3 +1,4 @@ +import {BaseException} from 'angular2/src/facade/exceptions'; import * as o from '../output/output_ast'; import {Identifiers, identifierToken} from '../identifiers'; import {InjectMethodVars} from './constants'; @@ -18,6 +19,7 @@ import { import {getPropertyInView, createDiTokenExpression, injectFromViewParentInjector} from './util'; import {CompileQuery, createQueryList, addQueryToTokenMap} from './compile_query'; import {CompileMethod} from './compile_method'; +import {ValueTransformer, visitValue} from '../util'; export class CompileNode { constructor(public parent: CompileElement, public view: CompileView, public nodeIndex: number, @@ -72,6 +74,7 @@ export class CompileElement extends CompileNode { private _createAppElement() { var fieldName = `_appEl_${this.nodeIndex}`; var parentNodeIndex = this.isRootElement() ? null : this.parent.nodeIndex; + // private is fine here as no child view will reference an AppElement this.view.fields.push(new o.ClassField(fieldName, o.importType(Identifiers.AppElement), [o.StmtModifier.Private])); var statement = o.THIS_EXPR.prop(fieldName) @@ -140,13 +143,7 @@ export class CompileElement extends CompileNode { return o.importExpr(provider.useClass) .instantiate(depsExpr, o.importType(provider.useClass)); } else { - if (provider.useValue instanceof CompileIdentifierMetadata) { - return o.importExpr(provider.useValue); - } else if (provider.useValue instanceof o.Expression) { - return provider.useValue; - } else { - return o.literal(provider.useValue); - } + return _convertValueToOutputAst(provider.useValue); } }); var propName = `_${resolvedProvider.token.name}_${this.nodeIndex}_${this._instances.size}`; @@ -379,11 +376,11 @@ function createProviderProperty(propName: string, provider: ProviderAst, type = o.DYNAMIC_TYPE; } if (isEager) { - view.fields.push(new o.ClassField(propName, type, [o.StmtModifier.Private])); + view.fields.push(new o.ClassField(propName, type)); view.createMethod.addStmt(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt()); } else { var internalField = `_${propName}`; - view.fields.push(new o.ClassField(internalField, type, [o.StmtModifier.Private])); + view.fields.push(new o.ClassField(internalField, type)); var getter = new CompileMethod(view); getter.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); // Note: Equals is important for JS so that it also checks the undefined case! @@ -402,3 +399,29 @@ class _QueryWithRead { this.read = isPresent(query.meta.read) ? query.meta.read : match; } } + +function _convertValueToOutputAst(value: any): o.Expression { + return visitValue(value, new _ValueOutputAstTransformer(), null); +} + +class _ValueOutputAstTransformer extends ValueTransformer { + visitArray(arr: any[], context: any): o.Expression { + return o.literalArr(arr.map(value => visitValue(value, this, context))); + } + visitStringMap(map: {[key: string]: any}, context: any): o.Expression { + var entries = []; + StringMapWrapper.forEach( + map, (value, key) => { entries.push([key, visitValue(value, this, context)]); }); + return o.literalMap(entries); + } + visitPrimitive(value: any, context: any): o.Expression { return o.literal(value); } + visitOther(value: any, context: any): o.Expression { + if (value instanceof CompileIdentifierMetadata) { + return o.importExpr(value); + } else if (value instanceof o.Expression) { + return value; + } else { + throw new BaseException(`Illegal state: Don't now how to compile value ${value}`); + } + } +} \ No newline at end of file diff --git a/modules/angular2/src/compiler/view_compiler/compile_pipe.ts b/modules/angular2/src/compiler/view_compiler/compile_pipe.ts index 8493aa3d55..63b456ed70 100644 --- a/modules/angular2/src/compiler/view_compiler/compile_pipe.ts +++ b/modules/angular2/src/compiler/view_compiler/compile_pipe.ts @@ -29,8 +29,7 @@ export class CompilePipe { } return injectFromViewParentInjector(diDep.token, false); }); - this.view.fields.push(new o.ClassField(this.instance.name, o.importType(this.meta.type), - [o.StmtModifier.Private])); + this.view.fields.push(new o.ClassField(this.instance.name, o.importType(this.meta.type))); this.view.createMethod.resetDebugInfo(null, null); this.view.createMethod.addStmt(o.THIS_EXPR.prop(this.instance.name) .set(o.importExpr(this.meta.type).instantiate(deps)) diff --git a/modules/angular2/src/compiler/view_compiler/compile_query.ts b/modules/angular2/src/compiler/view_compiler/compile_query.ts index 63a88e4e82..0819fc3141 100644 --- a/modules/angular2/src/compiler/view_compiler/compile_query.ts +++ b/modules/angular2/src/compiler/view_compiler/compile_query.ts @@ -97,8 +97,7 @@ function mapNestedViews(declarationAppElement: o.Expression, view: CompileView, export function createQueryList(query: CompileQueryMetadata, directiveInstance: o.Expression, propertyName: string, compileView: CompileView): o.Expression { - compileView.fields.push(new o.ClassField(propertyName, o.importType(Identifiers.QueryList), - [o.StmtModifier.Private])); + compileView.fields.push(new o.ClassField(propertyName, o.importType(Identifiers.QueryList))); var expr = o.THIS_EXPR.prop(propertyName); compileView.createMethod.addStmt(o.THIS_EXPR.prop(propertyName) .set(o.importExpr(Identifiers.QueryList).instantiate([])) diff --git a/modules/angular2/src/compiler/view_compiler/event_binder.ts b/modules/angular2/src/compiler/view_compiler/event_binder.ts index 8d6e4f9371..cf0c70c776 100644 --- a/modules/angular2/src/compiler/view_compiler/event_binder.ts +++ b/modules/angular2/src/compiler/view_compiler/event_binder.ts @@ -77,6 +77,7 @@ export class CompileEventListener { ([markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt()]) .concat(this._method.finish()) .concat([new o.ReturnStatement(resultExpr)]); + // private is fine here as no child view will reference the event handler... this.compileElement.view.eventHandlerMethods.push(new o.ClassMethod( this._methodName, [this._eventParam], stmts, o.BOOL_TYPE, [o.StmtModifier.Private])); } @@ -95,6 +96,7 @@ export class CompileEventListener { } var disposable = o.variable(`disposable_${this.compileElement.view.disposables.length}`); this.compileElement.view.disposables.push(disposable); + // private is fine here as no child view will reference the event handler... this.compileElement.view.createMethod.addStmt( disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private])); } diff --git a/modules/angular2/src/compiler/view_compiler/property_binder.ts b/modules/angular2/src/compiler/view_compiler/property_binder.ts index ac4971a239..6be64c3381 100644 --- a/modules/angular2/src/compiler/view_compiler/property_binder.ts +++ b/modules/angular2/src/compiler/view_compiler/property_binder.ts @@ -43,6 +43,7 @@ function bind(view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPr return; } + // private is fine here as no child view will reference the cached value... view.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private])); view.createMethod.addStmt( o.THIS_EXPR.prop(fieldExpr.name).set(o.importExpr(Identifiers.uninitialized)).toStmt()); diff --git a/modules/angular2/src/compiler/view_compiler/util.ts b/modules/angular2/src/compiler/view_compiler/util.ts index 94d76ba2f9..4bef3ed943 100644 --- a/modules/angular2/src/compiler/view_compiler/util.ts +++ b/modules/angular2/src/compiler/view_compiler/util.ts @@ -88,7 +88,7 @@ export function createFlatArray(expressions: o.Expression[]): o.Expression { export function createPureProxy(fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr, view: CompileView) { - view.fields.push(new o.ClassField(pureProxyProp.name, null, [o.StmtModifier.Private])); + view.fields.push(new o.ClassField(pureProxyProp.name, null)); var pureProxyId = argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null; if (isBlank(pureProxyId)) { diff --git a/modules/angular2/src/compiler/view_compiler/view_builder.ts b/modules/angular2/src/compiler/view_compiler/view_builder.ts index f4a45bd014..2fe73159e7 100644 --- a/modules/angular2/src/compiler/view_compiler/view_builder.ts +++ b/modules/angular2/src/compiler/view_compiler/view_builder.ts @@ -128,9 +128,8 @@ class ViewBuilderVisitor implements TemplateAstVisitor { private _visitText(ast: TemplateAst, value: string, ngContentIndex: number, parent: CompileElement): o.Expression { var fieldName = `_text_${this.view.nodes.length}`; - this.view.fields.push(new o.ClassField(fieldName, - o.importType(this.view.genConfig.renderTypes.renderText), - [o.StmtModifier.Private])); + this.view.fields.push( + new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderText))); var renderNode = o.THIS_EXPR.prop(fieldName); var compileNode = new CompileNode(parent, this.view, this.view.nodes.length, renderNode, ast); var createRenderNode = @@ -194,8 +193,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor { } var fieldName = `_el_${nodeIndex}`; this.view.fields.push( - new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderElement), - [o.StmtModifier.Private])); + new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderElement))); this.view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName).set(createRenderNodeExpr).toStmt()); var renderNode = o.THIS_EXPR.prop(fieldName); @@ -257,8 +255,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor { var nodeIndex = this.view.nodes.length; var fieldName = `_anchor_${nodeIndex}`; this.view.fields.push( - new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderComment), - [o.StmtModifier.Private])); + new o.ClassField(fieldName, o.importType(this.view.genConfig.renderTypes.renderComment))); this.view.createMethod.addStmt( o.THIS_EXPR.prop(fieldName) .set(ViewProperties.renderer.callMethod( diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index b03a7114f9..0ac64ad387 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -26,6 +26,7 @@ bool isString(Object obj) => obj is String; bool isFunction(Object obj) => obj is Function; bool isType(Object obj) => obj is Type; bool isStringMap(Object obj) => obj is Map; +bool isStrictStringMap(Object obj) => obj is Map; bool isArray(Object obj) => obj is List; bool isPromise(Object obj) => obj is Future; bool isNumber(Object obj) => obj is num; diff --git a/modules/angular2/src/facade/lang.ts b/modules/angular2/src/facade/lang.ts index 3c0de8f692..118251dd06 100644 --- a/modules/angular2/src/facade/lang.ts +++ b/modules/angular2/src/facade/lang.ts @@ -138,6 +138,11 @@ export function isStringMap(obj: any): boolean { return typeof obj === 'object' && obj !== null; } +const STRING_MAP_PROTO = Object.getPrototypeOf({}); +export function isStrictStringMap(obj: any): boolean { + return isStringMap(obj) && Object.getPrototypeOf(obj) === STRING_MAP_PROTO; +} + export function isPromise(obj: any): boolean { return obj instanceof (_global).Promise; } diff --git a/modules/angular2/test/core/linker/view_injector_integration_spec.ts b/modules/angular2/test/core/linker/view_injector_integration_spec.ts index eeff6a620e..b3181003d3 100644 --- a/modules/angular2/test/core/linker/view_injector_integration_spec.ts +++ b/modules/angular2/test/core/linker/view_injector_integration_spec.ts @@ -314,6 +314,27 @@ export function main() { expect(d.dependency).toBeAnInstanceOf(SimpleDirective); })); + it('should support useValue with different values', fakeAsync(() => { + var el = createComp('', tcb.overrideProviders(TestComp, [ + provide('numLiteral', {useValue: 0}), + provide('boolLiteral', {useValue: true}), + provide('strLiteral', {useValue: 'a'}), + provide('null', {useValue: null}), + provide('array', {useValue: [1]}), + provide('map', {useValue: {'a': 1}}), + provide('instance', {useValue: new TestValue('a')}), + provide('nested', {useValue: [{'a': [1]}, new TestValue('b')]}), + ])); + expect(el.inject('numLiteral')).toBe(0); + expect(el.inject('boolLiteral')).toBe(true); + expect(el.inject('strLiteral')).toBe('a'); + expect(el.inject('null')).toBe(null); + expect(el.inject('array')).toEqual([1]); + expect(el.inject('map')).toEqual({'a': 1}); + expect(el.inject('instance')).toEqual(new TestValue('a')); + expect(el.inject('nested')).toEqual([{'a': [1]}, new TestValue('b')]); + })); + it("should instantiate providers that have dependencies with SkipSelf", fakeAsync(() => { var el = createComp('
', tcb.overrideProviders( @@ -679,3 +700,7 @@ export function main() { }); }); } + +class TestValue { + constructor(public value: string) {} +} \ No newline at end of file diff --git a/tools/compiler_cli/test/src/basic.ts b/tools/compiler_cli/test/src/basic.ts index 55dccdae5a..b1b055ed7c 100644 --- a/tools/compiler_cli/test/src/basic.ts +++ b/tools/compiler_cli/test/src/basic.ts @@ -1,14 +1,9 @@ -import {Component, Injectable} from 'angular2/core'; +import {Component, Inject} from 'angular2/core'; import {FORM_DIRECTIVES} from 'angular2/common'; import {MyComp} from './a/multiple_components'; -@Component({ - selector: 'basic', - templateUrl: './basic.html', - directives: [MyComp, FORM_DIRECTIVES], -}) -@Injectable() +@Component({selector: 'basic', templateUrl: './basic.html', directives: [MyComp, FORM_DIRECTIVES]}) export class Basic { ctxProp: string; - constructor() { this.ctxProp = 'initial value'; } + constructor() { this.ctxProp = 'initiaValue'; } } diff --git a/tools/compiler_cli/test/src/features.ts b/tools/compiler_cli/test/src/features.ts new file mode 100644 index 0000000000..73ab805f25 --- /dev/null +++ b/tools/compiler_cli/test/src/features.ts @@ -0,0 +1,29 @@ +import {Component, Inject, OpaqueToken} from 'angular2/core'; +import {NgIf} from 'angular2/common'; + +export const SOME_OPAQUE_TOKEN = new OpaqueToken('opaqueToken'); + +@Component({ + selector: 'comp-providers', + template: '', + providers: [ + {provide: 'strToken', useValue: 'strValue'}, + {provide: SOME_OPAQUE_TOKEN, useValue: 10}, + {provide: 'reference', useValue: NgIf}, + {provide: 'complexToken', useValue: {a: 1, b: ['test', SOME_OPAQUE_TOKEN]}}, + ] +}) +export class CompWithProviders { + constructor(@Inject('strToken') public ctxProp) {} +} + +@Component({ + selector: 'cmp-reference', + template: ` + {{a.value}} +
{{a.value}}
+ `, + directives: [NgIf] +}) +export class CompWithReferences { +}