feat(compiler): implement style encapsulation for new view engine (#14518)

Included refactoring:
- splits the `RendererV2` into a `RendererFactoryV2` and a `RendererV2`
- makes the `DebugRendererV2` a private class in `@angular/core`
- remove `setBindingDebugInfo` from `RendererV2`, but rename `RendererV2.setText` to 
  `RendererV2.setValue` and allow it on comments and text nodes.

Part of #14013
This commit is contained in:
Tobias Bosch
2017-02-16 13:55:55 -08:00
committed by Igor Minar
parent ba17dcbf2b
commit 0fa3895d5b
38 changed files with 828 additions and 595 deletions

View File

@ -9,8 +9,8 @@
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RENDERER_V2_DIRECT, RenderComponentType, Renderer, RendererV2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {DebugDomRenderer, DebugDomRendererV2} from '@angular/core/src/debug/debug_renderer';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RendererFactoryV2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {DebugDomRenderer} from '@angular/core/src/debug/debug_renderer';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -57,6 +57,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
renderLog = TestBed.get(RenderLog);
directiveLog = TestBed.get(DirectiveLog);
elSchema.existingProperties['someProp'] = true;
patchLoggingRendererV2(TestBed.get(RendererFactoryV2), renderLog);
}
function queryDirs(el: DebugElement, dirType: Type<any>): any {
@ -123,11 +124,6 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
RenderLog,
DirectiveLog,
{provide: RootRenderer, useClass: LoggingRootRenderer},
{
provide: RendererV2,
useFactory: (r: RendererV2, log: RenderLog) => new LoggingRendererV2(r, log),
deps: [[new Inject(RENDERER_V2_DIRECT)], [RenderLog]],
},
],
});
});
@ -1244,28 +1240,26 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
expect(renderLog.loggedValues).toEqual(['Tom']);
});
// TODO(tbosch): ViewQueries don't work yet with the view engine...
viewEngine ||
it('should recurse into nested view containers even if there are no bindings in the component view',
() => {
@Component({template: '<template #vc>{{name}}</template>'})
class Comp {
name = 'Tom';
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
@ViewChild(TemplateRef) template: TemplateRef<any>;
}
it('should recurse into nested view containers even if there are no bindings in the component view',
() => {
@Component({template: '<template #vc>{{name}}</template>'})
class Comp {
name = 'Tom';
@ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef;
@ViewChild(TemplateRef) template: TemplateRef<any>;
}
TestBed.configureTestingModule({declarations: [Comp]});
initHelpers();
TestBed.configureTestingModule({declarations: [Comp]});
initHelpers();
const ctx = TestBed.createComponent(Comp);
ctx.detectChanges();
expect(renderLog.loggedValues).toEqual([]);
const ctx = TestBed.createComponent(Comp);
ctx.detectChanges();
expect(renderLog.loggedValues).toEqual([]);
ctx.componentInstance.vc.createEmbeddedView(ctx.componentInstance.template);
ctx.detectChanges();
expect(renderLog.loggedValues).toEqual(['Tom']);
});
ctx.componentInstance.vc.createEmbeddedView(ctx.componentInstance.template);
ctx.detectChanges();
expect(renderLog.loggedValues).toEqual(['Tom']);
});
});
});
}
@ -1315,18 +1309,32 @@ class DirectiveLogEntry {
constructor(public directiveName: string, public method: string) {}
}
class LoggingRendererV2 extends DebugDomRendererV2 {
constructor(private delegate: RendererV2, private log: RenderLog) { super(delegate); }
setProperty(el: any, name: string, value: any): void {
this.log.setElementProperty(el, name, value);
super.setProperty(el, name, value);
}
setText(node: any, value: string): void {
this.log.setText(node, value);
super.setText(node, value);
function patchLoggingRendererV2(rendererFactory: RendererFactoryV2, log: RenderLog) {
if ((<any>rendererFactory).__patchedForLogging) {
return;
}
(<any>rendererFactory).__patchedForLogging = true;
const origCreateRenderer = rendererFactory.createRenderer;
rendererFactory.createRenderer = function() {
const renderer = origCreateRenderer.apply(this, arguments);
if ((<any>renderer).__patchedForLogging) {
return renderer;
}
(<any>renderer).__patchedForLogging = true;
const origSetProperty = renderer.setProperty;
const origSetValue = renderer.setValue;
renderer.setProperty = function(el: any, name: string, value: any): void {
log.setElementProperty(el, name, value);
origSetProperty.call(this, el, name, value);
};
renderer.setValue = function(node: any, value: string): void {
if (getDOM().isTextNode(node)) {
log.setText(node, value);
}
origSetValue.call(this, node, value);
};
return renderer;
};
}
@Injectable()

View File

@ -1515,7 +1515,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
const fixture = TestBed.createComponent(ParentCmp);
fixture.detectChanges();
expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('ng-reflect-test$="hello"');
expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('ng-reflect-test_="hello"');
});
it('should reflect property values on template comments', () => {

View File

@ -378,7 +378,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
expect(main.nativeElement).toHaveText('TREE(0:TREE2(1:TREE(2:)))');
});
if (!viewEngine && getDOM().supportsNativeShadowDOM()) {
if (getDOM().supportsNativeShadowDOM()) {
it('should support native content projection and isolate styles per component', () => {
TestBed.configureTestingModule({declarations: [SimpleNative1, SimpleNative2]});
TestBed.overrideComponent(MainComp, {
@ -396,7 +396,7 @@ function createTests({viewEngine}: {viewEngine: boolean}) {
});
}
if (!viewEngine && getDOM().supportsDOMEvents()) {
if (getDOM().supportsDOMEvents()) {
it('should support non emulated styles', () => {
TestBed.configureTestingModule({declarations: [OtherComp]});
TestBed.overrideComponent(MainComp, {