fix(UpgradeNg1ComponentAdapter): make bindings available on $scope in controller & link function (#8645)
Delays NG1 Directive controller instatiation where possible and pre-link function always to the ngOnInit() lifecycle hook. This way bindings are always available on $scope in both the controller and the link function.
This commit is contained in:
parent
15ae710d22
commit
6cdc53c497
|
@ -186,33 +186,22 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
|
||||||
checkLastValues: any[] = [];
|
checkLastValues: any[] = [];
|
||||||
componentScope: angular.IScope;
|
componentScope: angular.IScope;
|
||||||
element: Element;
|
element: Element;
|
||||||
|
$element: any = null;
|
||||||
|
|
||||||
constructor(private linkFn: angular.ILinkFn, scope: angular.IScope,
|
constructor(private linkFn: angular.ILinkFn, scope: angular.IScope,
|
||||||
private directive: angular.IDirective, elementRef: ElementRef,
|
private directive: angular.IDirective, elementRef: ElementRef,
|
||||||
$controller: angular.IControllerService, private inputs: string[],
|
private $controller: angular.IControllerService, private inputs: string[],
|
||||||
private outputs: string[], private propOuts: string[],
|
private outputs: string[], private propOuts: string[],
|
||||||
private checkProperties: string[], private propertyMap: {[key: string]: string}) {
|
private checkProperties: string[], private propertyMap: {[key: string]: string}) {
|
||||||
this.element = elementRef.nativeElement;
|
this.element = elementRef.nativeElement;
|
||||||
this.componentScope = scope.$new(!!directive.scope);
|
this.componentScope = scope.$new(!!directive.scope);
|
||||||
var $element = angular.element(this.element);
|
this.$element = angular.element(this.element);
|
||||||
var controllerType = directive.controller;
|
var controllerType = directive.controller;
|
||||||
var controller: any = null;
|
if (directive.bindToController && controllerType) {
|
||||||
if (controllerType) {
|
this.destinationObj = this.buildController(controllerType);
|
||||||
var locals = {$scope: this.componentScope, $element: $element};
|
} else {
|
||||||
controller = $controller(controllerType, locals, null, directive.controllerAs);
|
this.destinationObj = this.componentScope;
|
||||||
$element.data(controllerKey(directive.name), controller);
|
|
||||||
}
|
}
|
||||||
var link = directive.link;
|
|
||||||
if (typeof link == 'object') link = (<angular.IDirectivePrePost>link).pre;
|
|
||||||
if (link) {
|
|
||||||
var attrs: angular.IAttributes = NOT_SUPPORTED;
|
|
||||||
var transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
|
||||||
var linkController = this.resolveRequired($element, directive.require);
|
|
||||||
(<angular.IDirectiveLinkFn>directive.link)(this.componentScope, $element, attrs,
|
|
||||||
linkController, transcludeFn);
|
|
||||||
}
|
|
||||||
this.destinationObj =
|
|
||||||
directive.bindToController && controller ? controller : this.componentScope;
|
|
||||||
|
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
this[inputs[i]] = null;
|
this[inputs[i]] = null;
|
||||||
|
@ -225,9 +214,24 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
|
||||||
this[propOuts[k]] = new EventEmitter();
|
this[propOuts[k]] = new EventEmitter();
|
||||||
this.checkLastValues.push(INITIAL_VALUE);
|
this.checkLastValues.push(INITIAL_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
|
if (!this.directive.bindToController && this.directive.controller) {
|
||||||
|
this.buildController(this.directive.controller)
|
||||||
|
}
|
||||||
|
var link = this.directive.link;
|
||||||
|
if (typeof link == 'object') link = (<angular.IDirectivePrePost>link).pre;
|
||||||
|
if (link) {
|
||||||
|
var attrs: angular.IAttributes = NOT_SUPPORTED;
|
||||||
|
var transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
||||||
|
var linkController = this.resolveRequired(this.$element, this.directive.require);
|
||||||
|
(<angular.IDirectiveLinkFn>this.directive.link)(this.componentScope, this.$element, attrs,
|
||||||
|
linkController, transcludeFn);
|
||||||
|
}
|
||||||
|
|
||||||
var childNodes: Node[] = [];
|
var childNodes: Node[] = [];
|
||||||
var childNode;
|
var childNode;
|
||||||
while (childNode = this.element.firstChild) {
|
while (childNode = this.element.firstChild) {
|
||||||
|
@ -277,6 +281,13 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
|
||||||
this.destinationObj[this.propertyMap[name]] = value;
|
this.destinationObj[this.propertyMap[name]] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildController(controllerType) {
|
||||||
|
var locals = { $scope: this.componentScope, $element: this.$element };
|
||||||
|
var controller:any = this.$controller(controllerType, locals, null, this.directive.controllerAs);
|
||||||
|
this.$element.data(controllerKey(this.directive.name), controller);
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
private resolveRequired($element: angular.IAugmentedJQuery, require: string | string[]): any {
|
private resolveRequired($element: angular.IAugmentedJQuery, require: string | string[]): any {
|
||||||
if (!require) {
|
if (!require) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -314,6 +314,94 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should bind properties, events in controller when bindToController is not used', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
var ng1 = function() {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
template: '{{someText}} - Length: {{data.length}}',
|
||||||
|
scope: { data: "="},
|
||||||
|
controller: function($scope) {
|
||||||
|
$scope.someText = "ng1 - Data: " + $scope.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')],
|
||||||
|
})
|
||||||
|
.Class({
|
||||||
|
|
||||||
|
constructor: function() {
|
||||||
|
this.dataList = [1, 2, 3];
|
||||||
|
this.someText = "ng2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||||
|
// events, and so without this we would not see the events processed.
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(multiTrim(document.body.textContent))
|
||||||
|
.toEqual(
|
||||||
|
"ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3");
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should bind properties, events in link function', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
var ng1 = function() {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
template: '{{someText}} - Length: {{data.length}}',
|
||||||
|
scope: { data: "=" },
|
||||||
|
link: function($scope) {
|
||||||
|
$scope.someText = "ng1 - Data: " + $scope.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '{{someText}} - Length: {{dataList.length}} | <ng1 [(data)]="dataList"></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')],
|
||||||
|
})
|
||||||
|
.Class({
|
||||||
|
|
||||||
|
constructor: function() {
|
||||||
|
this.dataList = [1, 2, 3];
|
||||||
|
this.someText = "ng2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||||
|
// events, and so without this we would not see the events processed.
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(multiTrim(document.body.textContent))
|
||||||
|
.toEqual(
|
||||||
|
"ng2 - Length: 3 | ng1 - Data: 1,2,3 - Length: 3");
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should support templateUrl fetched from $httpBackend',
|
it('should support templateUrl fetched from $httpBackend',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var adapter = new UpgradeAdapter();
|
var adapter = new UpgradeAdapter();
|
||||||
|
|
Loading…
Reference in New Issue