Skip to content

Commit

Permalink
fix(upgrade): fix downgrade content projection and injector inheritance
Browse files Browse the repository at this point in the history
- Full support for content projection in downgraded Angular 2
  components. In particular, this enables multi-slot projection and
  other features on <ng-content>.
- Correctly wire up hierarchical injectors for downgraded Angular 2
  components: downgraded components inherit the injector of the first
  other downgraded Angular 2 component they find up the DOM tree.

Closes angular#6629, angular#7727, angular#8729, angular#9643, angular#9649
  • Loading branch information
youdz authored and petebacondarwin committed Nov 18, 2016
1 parent aa8509c commit 71d8609
Show file tree
Hide file tree
Showing 8 changed files with 465 additions and 184 deletions.
2 changes: 1 addition & 1 deletion modules/@angular/upgrade/src/angular_js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface IModule {
run(a: IInjectable): IModule;
}
export interface ICompileService {
(element: Element|NodeList|string, transclude?: Function): ILinkFn;
(element: Element|NodeList|Node[]|string, transclude?: Function): ILinkFn;
}
export interface ILinkFn {
(scope: IScope, cloneAttachFn?: ICloneAttachFunction, options?: ILinkFnOptions): IAugmentedJQuery;
Expand Down
3 changes: 2 additions & 1 deletion modules/@angular/upgrade/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const NG2_COMPILER = 'ng2.Compiler';
export const NG2_INJECTOR = 'ng2.Injector';
export const NG2_COMPONENT_FACTORY_REF_MAP = 'ng2.ComponentFactoryRefMap';
export const NG2_ZONE = 'ng2.NgZone';
export const NG2_CAPTURED_CONTENT_SELECTORS = 'ng2.CapturedContentSelectors';

export const NG1_PROVIDE = '$provide';
export const NG1_CONTROLLER = '$controller';
Expand All @@ -21,4 +22,4 @@ export const NG1_INJECTOR = '$injector';
export const NG1_PARSE = '$parse';
export const NG1_TEMPLATE_CACHE = '$templateCache';
export const NG1_TESTABILITY = '$$testability';
export const REQUIRE_INJECTOR = '?^' + NG2_INJECTOR;
export const REQUIRE_INJECTOR = '?^^' + NG2_INJECTOR;
25 changes: 6 additions & 19 deletions modules/@angular/upgrade/src/downgrade_ng2_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,23 @@ export class DowngradeNg2ComponentAdapter {
componentRef: ComponentRef<any> = null;
changeDetector: ChangeDetectorRef = null;
componentScope: angular.IScope;
childNodes: Node[];
contentInsertionPoint: Node = null;
elementInjector: Injector;

constructor(
private id: string, private info: ComponentInfo, private element: angular.IAugmentedJQuery,
private info: ComponentInfo, private element: angular.IAugmentedJQuery,
private attrs: angular.IAttributes, private scope: angular.IScope,
private parentInjector: Injector, private parse: angular.IParseService,
private componentFactory: ComponentFactory<any>) {
(<any>this.element[0]).id = id;
this.componentScope = scope.$new();
this.childNodes = <Node[]><any>element.contents();
}

bootstrapNg2() {
bootstrapNg2(projectableNodes: Node[][]) {
const childInjector = ReflectiveInjector.resolveAndCreate(
[{provide: NG1_SCOPE, useValue: this.componentScope}], this.parentInjector);
this.contentInsertionPoint = document.createComment('ng1 insertion point');

this.componentRef = this.componentFactory.create(
childInjector, [[this.contentInsertionPoint]], this.element[0]);
this.componentRef =
this.componentFactory.create(childInjector, projectableNodes, this.element[0]);
this.elementInjector = this.componentRef.injector;
this.changeDetector = this.componentRef.changeDetectorRef;
this.component = this.componentRef.instance;
}
Expand Down Expand Up @@ -103,16 +100,6 @@ export class DowngradeNg2ComponentAdapter {
this.componentScope.$watch(() => this.changeDetector && this.changeDetector.detectChanges());
}

projectContent() {
const childNodes = this.childNodes;
const parent = this.contentInsertionPoint.parentNode;
if (parent) {
for (let i = 0, ii = childNodes.length; i < ii; i++) {
parent.insertBefore(childNodes[i], this.contentInsertionPoint);
}
}
}

setupOutputs() {
const attrs = this.attrs;
const outputs = this.info.outputs || [];
Expand Down
24 changes: 24 additions & 0 deletions modules/@angular/upgrade/src/platform_upgrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {CompilerFactory, PlatformRef, Provider, createPlatformFactory} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {CapturedContentSelectors, RuntimeCompilerSpyFactory} from './runtime_compiler_spy';

const INTERNAL_UPGRADE_PLATFORM_PROVIDERS: Provider[] =
[CapturedContentSelectors, {provide: CompilerFactory, useClass: RuntimeCompilerSpyFactory}];

/**
* A custom platform for the UpgradeAdapter, which includes additional helper services
* such as the `CapturedContentSelectors` collection and a monkeypatched version of the
* `CompilerFactory`.
*
* @experimental
*/
export const platformUpgrade =
createPlatformFactory(platformBrowserDynamic, 'upgrade', INTERNAL_UPGRADE_PLATFORM_PROVIDERS);
55 changes: 55 additions & 0 deletions modules/@angular/upgrade/src/runtime_compiler_spy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {RuntimeCompilerFactory} from '@angular/compiler';
import {COMPILER_OPTIONS, Compiler, CompilerOptions, Inject, Injectable} from '@angular/core';

/**
* This class is a map from each component`s selector to that component's `ngContent` selectors.
* @internal
*/
export class CapturedContentSelectors {
private items: {[componentSelector: string]: string[]} = {};
set(componentSelector: string, ngContentSelectors: string[]) {
this.items[componentSelector] = ngContentSelectors;
}
get(componentSelector: string): string[] { return this.items[componentSelector]; }
}

/**
* We need to capture all the `ngContent` selectors for each downgraded component so that
* we can correctly project the content onto them.
* We do this by monkeypatching the `Compiler` with our own version that spies upon the
* compiled templates and captures all the `ngContent` selectors.
* @internal
*/
@Injectable()
export class RuntimeCompilerSpyFactory extends RuntimeCompilerFactory {
constructor(
@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[],
private contentSelectors: CapturedContentSelectors) {
super(defaultOptions);
}
createCompiler(options: CompilerOptions[] = []): Compiler {
// Very ugly hack to get access to the compiler's internal objects.
// Better here than in the compiler module itself, though.
const contentSelectors = this.contentSelectors;
let runtimeCompiler: any = <any>super.createCompiler(options);
let originalCompileTemplate = runtimeCompiler._compileTemplate;
runtimeCompiler._compileTemplate = function(template: any) {
if (!template.isCompiled && !template.isHost) {
const metadata = template.compMeta;
if (metadata.template && metadata.template.ngContentSelectors) {
contentSelectors.set(metadata.selector, metadata.template.ngContentSelectors);
}
}
return originalCompileTemplate.call(this, template);
};
return runtimeCompiler;
}
}
Loading

0 comments on commit 71d8609

Please sign in to comment.