fix(upgrade): make upgradeAdapter upgrade angular 1 components correctly

With this fix, the $onInit function of an upgraded angular 1 component is called and input bindings (<) are created.

Closes #7951
This commit is contained in:
Kevin Merckx 2016-03-14 07:51:04 +01:00 committed by Alex Rickabaugh
parent 5e2bc5c593
commit 247964af62
3 changed files with 79 additions and 0 deletions

View File

@ -1,6 +1,7 @@
export interface IModule { export interface IModule {
config(fn: any): IModule; config(fn: any): IModule;
directive(selector: string, factory: any): IModule; directive(selector: string, factory: any): IModule;
component(selector: string, component: IComponent): IModule;
controller(name: string, type: any): IModule; controller(name: string, type: any): IModule;
factory(key: string, factoryFn: any): IModule; factory(key: string, factoryFn: any): IModule;
value(key: string, value: any): IModule; value(key: string, value: any): IModule;
@ -59,6 +60,15 @@ export interface IDirectiveLinkFn {
(scope: IScope, instanceElement: IAugmentedJQuery, instanceAttributes: IAttributes, (scope: IScope, instanceElement: IAugmentedJQuery, instanceAttributes: IAttributes,
controller: any, transclude: ITranscludeFunction): void; controller: any, transclude: ITranscludeFunction): void;
} }
export interface IComponent {
bindings?: Object;
controller?: any;
controllerAs?: string;
require?: any;
template?: any;
templateUrl?: any;
transclude?: any;
}
export interface IAttributes { $observe(attr: string, fn: (v: string) => void): void; } export interface IAttributes { $observe(attr: string, fn: (v: string) => void): void; }
export interface ITranscludeFunction { export interface ITranscludeFunction {
// If the scope is provided, then the cloneAttachFn must be as well. // If the scope is provided, then the cloneAttachFn must be as well.

View File

@ -53,6 +53,7 @@ export class UpgradeNg1ComponentAdapterBuilder {
self.outputs, self.propertyOutputs, self.checkProperties, self.propertyMap); self.outputs, self.propertyOutputs, self.checkProperties, self.propertyMap);
} }
], ],
ngOnInit: function() { /* needs to be here for ng2 to properly detect it */ },
ngOnChanges: function() { /* needs to be here for ng2 to properly detect it */ }, ngOnChanges: function() { /* needs to be here for ng2 to properly detect it */ },
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ } ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ }
}); });
@ -106,6 +107,8 @@ export class UpgradeNg1ComponentAdapterBuilder {
this.propertyMap[outputName] = localName; this.propertyMap[outputName] = localName;
// don't break; let it fall through to '@' // don't break; let it fall through to '@'
case '@': case '@':
// handle the '<' binding of angular 1.5 components
case '<':
this.inputs.push(inputName); this.inputs.push(inputName);
this.inputsRename.push(inputNameRename); this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = localName; this.propertyMap[inputName] = localName;
@ -231,6 +234,13 @@ class UpgradeNg1ComponentAdapter implements OnChanges, DoCheck {
} }
} }
ngOnInit() {
if (this.destinationObj.$onInit) {
this.destinationObj.$onInit();
}
}
ngOnChanges(changes: {[name: string]: SimpleChange}) { ngOnChanges(changes: {[name: string]: SimpleChange}) {
for (var name in changes) { for (var name in changes) {
if ((<Object>changes).hasOwnProperty(name)) { if ((<Object>changes).hasOwnProperty(name)) {

View File

@ -561,6 +561,65 @@ export function main() {
}); });
})); }));
it('should call $onInit of components', inject([AsyncTestCompleter], (async) => {
var adapter = new UpgradeAdapter();
var ng1Module = angular.module('ng1', []);
var valueToFind = '$onInit';
var ng1 = {
bindings: {},
template: '{{$ctrl.value}}',
controller: Class(
{constructor: function() {}, $onInit: function() { this.value = valueToFind; }})
};
ng1Module.component('ng1', ng1);
var Ng2 = Component({
selector: 'ng2',
template: '<ng1></ng1>',
directives: [adapter.upgradeNg1Component('ng1')]
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
var element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1'])
.ready((ref) => {
expect(multiTrim(document.body.textContent)).toEqual(valueToFind);
ref.dispose();
async.done();
});
}));
it('should bind input properties (<) of components', inject([AsyncTestCompleter], (async) => {
var adapter = new UpgradeAdapter();
var ng1Module = angular.module('ng1', []);
var ng1 = {
bindings: {personProfile: '<'},
template: 'Hello {{$ctrl.personProfile.firstName}} {{$ctrl.personProfile.lastName}}',
controller: Class({constructor: function() {}})
};
ng1Module.component('ng1', ng1);
var Ng2 =
Component({
selector: 'ng2',
template: '<ng1 [personProfile]="goku"></ng1>',
directives: [adapter.upgradeNg1Component('ng1')]
})
.Class({
constructor: function() { this.goku = {firstName: 'GOKU', lastName: 'SAN'}; }
});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
var element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1'])
.ready((ref) => {
expect(multiTrim(document.body.textContent)).toEqual(`Hello GOKU SAN`);
ref.dispose();
async.done();
});
}));
}); });
describe('injection', () => { describe('injection', () => {