diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts
index 10e6cc750c..d7a06ed835 100644
--- a/packages/core/src/render3/view_engine_compatibility.ts
+++ b/packages/core/src/render3/view_engine_compatibility.ts
@@ -29,7 +29,7 @@ import {getParentInjectorTNode} from './node_util';
import {getLView, getPreviousOrParentTNode} from './state';
import {getParentInjectorView, hasParentInjector} from './util/injector_utils';
import {findComponentView} from './util/view_traversal_utils';
-import {getComponentViewByIndex, getNativeByTNode, isComponent, isLContainer, isRootView, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
+import {getComponentViewByIndex, getNativeByTNode, isComponent, isLContainer, isLView, isRootView, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
import {ViewRef} from './view_ref';
@@ -389,6 +389,7 @@ export function createViewRef(
return null !;
}
+/** Returns a Renderer2 (or throws when application was bootstrapped with Renderer3) */
function getOrCreateRenderer2(view: LView): Renderer2 {
const renderer = view[RENDERER];
if (isProceduralRenderer(renderer)) {
@@ -398,7 +399,12 @@ function getOrCreateRenderer2(view: LView): Renderer2 {
}
}
-/** Returns a Renderer2 (or throws when application was bootstrapped with Renderer3) */
+/** Injects a Renderer2 for the current component. */
export function injectRenderer2(): Renderer2 {
- return getOrCreateRenderer2(getLView());
+ // We need the Renderer to be based on the component that it's being injected into, however since
+ // DI happens before we've entered its view, `getLView` will return the parent view instead.
+ const lView = getLView();
+ const tNode = getPreviousOrParentTNode();
+ const nodeAtIndex = getComponentViewByIndex(tNode.index, lView);
+ return getOrCreateRenderer2(isLView(nodeAtIndex) ? nodeAtIndex : lView);
}
diff --git a/packages/core/test/acceptance/component_spec.ts b/packages/core/test/acceptance/component_spec.ts
index 7fe1e54939..be2040bdb6 100644
--- a/packages/core/test/acceptance/component_spec.ts
+++ b/packages/core/test/acceptance/component_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Component, ComponentFactoryResolver, ComponentRef, InjectionToken, NgModule, OnDestroy, Type, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core';
+import {Component, ComponentFactoryResolver, ComponentRef, ElementRef, InjectionToken, NgModule, OnDestroy, Renderer2, Type, ViewChild, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -178,4 +178,79 @@ describe('component', () => {
'Expected component onDestroy method to be called when its parent view is destroyed');
});
});
+
+ it('should use a new ngcontent attribute for child elements created w/ Renderer2', () => {
+ @Component({
+ selector: 'app-root',
+ template: '',
+ styles: [':host { color: red; }'], // `styles` must exist for encapsulation to apply.
+ encapsulation: ViewEncapsulation.Emulated,
+ })
+ class AppRoot {
+ }
+
+ @Component({
+ selector: 'parent-comp',
+ template: '',
+ styles: [':host { color: orange; }'], // `styles` must exist for encapsulation to apply.
+ encapsulation: ViewEncapsulation.Emulated,
+ })
+ class ParentComponent {
+ constructor(elementRef: ElementRef, renderer: Renderer2) {
+ const elementFromRenderer = renderer.createElement('p');
+ renderer.appendChild(elementRef.nativeElement, elementFromRenderer);
+ }
+ }
+
+ TestBed.configureTestingModule({declarations: [AppRoot, ParentComponent]});
+ const fixture = TestBed.createComponent(AppRoot);
+ fixture.detectChanges();
+
+ const secondParentEl: HTMLElement = fixture.nativeElement.querySelector('parent-comp');
+ const elementFromRenderer: HTMLElement = fixture.nativeElement.querySelector('p');
+ const getNgContentAttr = (element: HTMLElement) => {
+ return Array.from(element.attributes).map(a => a.name).find(a => /ngcontent/.test(a));
+ };
+
+ const hostNgContentAttr = getNgContentAttr(secondParentEl);
+ const viewNgContentAttr = getNgContentAttr(elementFromRenderer);
+
+ expect(hostNgContentAttr)
+ .not.toBe(
+ viewNgContentAttr,
+ 'Expected child manually created via Renderer2 to have a different view encapsulation' +
+ 'attribute than its host element');
+ });
+
+ it('should create a new Renderer2 for each component', () => {
+ @Component({
+ selector: 'child',
+ template: '',
+ styles: [':host { color: red; }'],
+ encapsulation: ViewEncapsulation.Emulated,
+ })
+ class Child {
+ constructor(public renderer: Renderer2) {}
+ }
+
+ @Component({
+ template: '',
+ styles: [':host { color: orange; }'],
+ encapsulation: ViewEncapsulation.Emulated,
+ })
+ class Parent {
+ @ViewChild(Child, {static: false}) childInstance !: Child;
+ constructor(public renderer: Renderer2) {}
+ }
+
+ TestBed.configureTestingModule({declarations: [Parent, Child]});
+ const fixture = TestBed.createComponent(Parent);
+ const componentInstance = fixture.componentInstance;
+ fixture.detectChanges();
+
+ // Assert like this, rather than `.not.toBe` so we get a better failure message.
+ expect(componentInstance.renderer !== componentInstance.childInstance.renderer)
+ .toBe(true, 'Expected renderers to be different.');
+ });
+
});