Skip to content

Commit bf3beb5

Browse files
ocombemhevery
authored andcommitted
fix(ivy): set ng-version attribute on root component (angular#27175)
PR Close angular#27175
1 parent 0191773 commit bf3beb5

File tree

4 files changed

+84
-78
lines changed

4 files changed

+84
-78
lines changed

packages/core/src/render3/component_ref.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
1616
import {NgModuleRef as viewEngine_NgModuleRef} from '../linker/ng_module_factory';
1717
import {RendererFactory2} from '../render/api';
1818
import {Type} from '../type';
19-
19+
import {VERSION} from '../version';
2020
import {assertComponentType, assertDefined} from './assert';
2121
import {LifecycleHooksFeature, createRootComponent, createRootComponentView, createRootContext} from './component';
2222
import {getComponentDef} from './definition';
2323
import {NodeInjector} from './di';
2424
import {createLViewData, createNodeAtIndex, createTView, createViewNode, elementCreate, locateHostElement, refreshDescendantViews} from './instructions';
2525
import {ComponentDef, RenderFlags} from './interfaces/definition';
26-
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
27-
import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
28-
import {FLAGS, HEADER_OFFSET, INJECTOR, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
26+
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType} from './interfaces/node';
27+
import {RElement, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
28+
import {HEADER_OFFSET, LViewData, LViewFlags, RootContext, TVIEW} from './interfaces/view';
2929
import {enterView, leaveView} from './state';
3030
import {defaultScheduler, getTNode} from './util';
3131
import {createElementRef} from './view_engine_compatibility';
@@ -141,6 +141,14 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
141141
const renderer = rendererFactory.createRenderer(hostRNode, this.componentDef);
142142
const rootViewInjector =
143143
ngModule ? createChainedInjector(injector, ngModule.injector) : injector;
144+
145+
if (rootSelectorOrNode && hostRNode) {
146+
ngDevMode && ngDevMode.rendererSetAttribute++;
147+
isProceduralRenderer(renderer) ?
148+
renderer.setAttribute(hostRNode, 'ng-version', VERSION.full) :
149+
hostRNode.setAttribute('ng-version', VERSION.full);
150+
}
151+
144152
// Create the root view. Uses empty TView and ContentTemplate.
145153
const rootView: LViewData = createLViewData(
146154
renderer, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, undefined,

packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@
440440
{
441441
"name": "UnsubscriptionErrorImpl"
442442
},
443+
{
444+
"name": "VERSION"
445+
},
443446
{
444447
"name": "VIEWS"
445448
},

packages/core/test/bundling/todo_r2/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,9 @@
926926
{
927927
"name": "VALID_ELEMENTS"
928928
},
929+
{
930+
"name": "VERSION"
931+
},
929932
{
930933
"name": "VIEWS"
931934
},

packages/platform-server/test/integration_spec.ts

Lines changed: 66 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -539,32 +539,29 @@ class HiddenModule {
539539
});
540540
afterEach(() => { expect(called).toBe(true); });
541541

542-
fixmeIvy('to investigate') &&
543-
it('using long form should work', async(() => {
544-
const platform =
545-
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
546-
547-
platform.bootstrapModule(AsyncServerModule)
548-
.then((moduleRef) => {
549-
const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
550-
return applicationRef.isStable.pipe(first((isStable: boolean) => isStable))
551-
.toPromise();
552-
})
553-
.then((b) => {
554-
expect(platform.injector.get(PlatformState).renderToString())
555-
.toBe(expectedOutput);
556-
platform.destroy();
557-
called = true;
558-
});
559-
}));
560-
561-
fixmeIvy('to investigate') &&
562-
it('using renderModule should work', async(() => {
563-
renderModule(AsyncServerModule, {document: doc}).then(output => {
564-
expect(output).toBe(expectedOutput);
542+
it('using long form should work', async(() => {
543+
const platform =
544+
platformDynamicServer([{provide: INITIAL_CONFIG, useValue: {document: doc}}]);
545+
546+
platform.bootstrapModule(AsyncServerModule)
547+
.then((moduleRef) => {
548+
const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef);
549+
return applicationRef.isStable.pipe(first((isStable: boolean) => isStable))
550+
.toPromise();
551+
})
552+
.then((b) => {
553+
expect(platform.injector.get(PlatformState).renderToString()).toBe(expectedOutput);
554+
platform.destroy();
565555
called = true;
566556
});
567-
}));
557+
}));
558+
559+
it('using renderModule should work', async(() => {
560+
renderModule(AsyncServerModule, {document: doc}).then(output => {
561+
expect(output).toBe(expectedOutput);
562+
called = true;
563+
});
564+
}));
568565

569566
it('using renderModuleFactory should work',
570567
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
@@ -609,14 +606,13 @@ class HiddenModule {
609606
}));
610607

611608

612-
fixmeIvy('to investigate') &&
613-
it('sets a prefix for the _nghost and _ngcontent attributes', async(() => {
614-
renderModule(ExampleStylesModule, {document: doc}).then(output => {
615-
expect(output).toMatch(
616-
/<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/);
617-
called = true;
618-
});
619-
}));
609+
it('sets a prefix for the _nghost and _ngcontent attributes', async(() => {
610+
renderModule(ExampleStylesModule, {document: doc}).then(output => {
611+
expect(output).toMatch(
612+
/<html><head><style ng-transition="example-styles">div\[_ngcontent-sc\d+\] {color: blue; } \[_nghost-sc\d+\] { color: red; }<\/style><\/head><body><app _nghost-sc\d+="" ng-version="0.0.0-PLACEHOLDER"><div _ngcontent-sc\d+="">Works!<\/div><\/app><\/body><\/html>/);
613+
called = true;
614+
});
615+
}));
620616

621617
fixmeIvy('to investigate') &&
622618
it('should handle false values on attributes', async(() => {
@@ -662,29 +658,27 @@ class HiddenModule {
662658
});
663659
}));
664660

665-
fixmeIvy('to investigate') &&
666-
it('should call render hook', async(() => {
667-
renderModule(RenderHookModule, {document: doc}).then(output => {
668-
// title should be added by the render hook.
669-
expect(output).toBe(
670-
'<html><head><title>RenderHook</title></head><body>' +
671-
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
672-
called = true;
673-
});
674-
}));
661+
it('should call render hook', async(() => {
662+
renderModule(RenderHookModule, {document: doc}).then(output => {
663+
// title should be added by the render hook.
664+
expect(output).toBe(
665+
'<html><head><title>RenderHook</title></head><body>' +
666+
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
667+
called = true;
668+
});
669+
}));
675670

676-
fixmeIvy('to investigate') &&
677-
it('should call multiple render hooks', async(() => {
678-
const consoleSpy = spyOn(console, 'warn');
679-
renderModule(MultiRenderHookModule, {document: doc}).then(output => {
680-
// title should be added by the render hook.
681-
expect(output).toBe(
682-
'<html><head><title>RenderHook</title><meta name="description"></head>' +
683-
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
684-
expect(consoleSpy).toHaveBeenCalled();
685-
called = true;
686-
});
687-
}));
671+
it('should call multiple render hooks', async(() => {
672+
const consoleSpy = spyOn(console, 'warn');
673+
renderModule(MultiRenderHookModule, {document: doc}).then(output => {
674+
// title should be added by the render hook.
675+
expect(output).toBe(
676+
'<html><head><title>RenderHook</title><meta name="description"></head>' +
677+
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
678+
expect(consoleSpy).toHaveBeenCalled();
679+
called = true;
680+
});
681+
}));
688682
});
689683

690684
describe('http', () => {
@@ -856,13 +850,12 @@ class HiddenModule {
856850
beforeEach(() => { called = false; });
857851
afterEach(() => { expect(called).toBe(true); });
858852

859-
fixmeIvy('to investigate') &&
860-
it('adds transfer script tag when using renderModule', async(() => {
861-
renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => {
862-
expect(output).toBe(defaultExpectedOutput);
863-
called = true;
864-
});
865-
}));
853+
it('adds transfer script tag when using renderModule', async(() => {
854+
renderModule(TransferStoreModule, {document: '<app></app>'}).then(output => {
855+
expect(output).toBe(defaultExpectedOutput);
856+
called = true;
857+
});
858+
}));
866859

867860
it('adds transfer script tag when using renderModuleFactory',
868861
async(inject([PlatformRef], (defaultPlatform: PlatformRef) => {
@@ -876,19 +869,18 @@ class HiddenModule {
876869
});
877870
})));
878871

879-
fixmeIvy('to investigate') &&
880-
it('cannot break out of <script> tag in serialized output', async(() => {
881-
renderModule(EscapedTransferStoreModule, {
882-
document: '<esc-app></esc-app>'
883-
}).then(output => {
884-
expect(output).toBe(
885-
'<html><head></head><body><esc-app ng-version="0.0.0-PLACEHOLDER">Works!</esc-app>' +
886-
'<script id="transfer-state" type="application/json">' +
887-
'{&q;testString&q;:&q;&l;/script&g;&l;script&g;' +
888-
'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}</script></body></html>');
889-
called = true;
890-
});
891-
}));
872+
it('cannot break out of <script> tag in serialized output', async(() => {
873+
renderModule(EscapedTransferStoreModule, {
874+
document: '<esc-app></esc-app>'
875+
}).then(output => {
876+
expect(output).toBe(
877+
'<html><head></head><body><esc-app ng-version="0.0.0-PLACEHOLDER">Works!</esc-app>' +
878+
'<script id="transfer-state" type="application/json">' +
879+
'{&q;testString&q;:&q;&l;/script&g;&l;script&g;' +
880+
'alert(&s;Hello&a;&s; + \\&q;World\\&q;);&q;}</script></body></html>');
881+
called = true;
882+
});
883+
}));
892884
});
893885
});
894886
})();

0 commit comments

Comments
 (0)