mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Detailed changes:
- remove `UNINITIALIZED`, initialize change detection fields with `undefined`.
* we use `view.numberOfChecks === 0` now everywhere
as indicator whether we are in the first change detection cycle
(previously we used this only in a couple of places).
* we keep the initialization itself as change detection get slower without it.
- remove passing around `throwOnChange` in various generated calls,
and store it on the view as property instead.
- change generated code for bindings to DOM elements as follows:
Before:
```
var currVal_10 = self.context.bgColor;
if (jit_checkBinding15(self.throwOnChange,self._expr_10,currVal_10)) {
self.renderer.setElementStyle(self._el_0,'backgroundColor',((self.viewUtils.sanitizer.sanitize(jit_21,currVal_10) == null)? null: self.viewUtils.sanitizer.sanitize(jit_21,currVal_10).toString()));
self._expr_10 = currVal_10;
}
var currVal_11 = jit_inlineInterpolate16(1,' ',self.context.data.value,' ');
if (jit_checkBinding15(self.throwOnChange,self._expr_11,currVal_11)) {
self.renderer.setText(self._text_1,currVal_11);
self._expr_11 = currVal_11;
}
```,
After:
```
var currVal_10 = self.context.bgColor;
jit_checkRenderStyle14(self,self._el_0,'backgroundColor',null,self._expr_10,self._expr_10=currVal_10,false,jit_21);
var currVal_11 = jit_inlineInterpolate15(1,' ',self.context.data.value,' ');
jit_checkRenderText16(self,self._text_1,self._expr_11,self._expr_11=currVal_11,false);
```
Performance impact:
- None seen (checked against internal latency lab)
Part of #13651
175 lines
6.5 KiB
TypeScript
175 lines
6.5 KiB
TypeScript
/**
|
|
* @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 {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
|
|
|
import * as angular from '../angular_js';
|
|
|
|
import {ComponentInfo, PropertyBinding} from './component_info';
|
|
import {$SCOPE} from './constants';
|
|
|
|
const INITIAL_VALUE = {
|
|
__UNINITIALIZED__: true
|
|
};
|
|
|
|
export class DowngradeComponentAdapter {
|
|
component: any = null;
|
|
inputs: Attr;
|
|
inputChangeCount: number = 0;
|
|
inputChanges: SimpleChanges = null;
|
|
componentRef: ComponentRef<any> = null;
|
|
changeDetector: ChangeDetectorRef = null;
|
|
componentScope: angular.IScope;
|
|
childNodes: Node[];
|
|
contentInsertionPoint: Node = null;
|
|
|
|
constructor(
|
|
private id: string, 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();
|
|
}
|
|
|
|
createComponent() {
|
|
const childInjector = ReflectiveInjector.resolveAndCreate(
|
|
[{provide: $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.changeDetector = this.componentRef.changeDetectorRef;
|
|
this.component = this.componentRef.instance;
|
|
}
|
|
|
|
setupInputs(): void {
|
|
const attrs = this.attrs;
|
|
const inputs = this.info.inputs || [];
|
|
for (let i = 0; i < inputs.length; i++) {
|
|
const input = new PropertyBinding(inputs[i]);
|
|
let expr: any /** TODO #9100 */ = null;
|
|
|
|
if (attrs.hasOwnProperty(input.attr)) {
|
|
const observeFn = ((prop: any /** TODO #9100 */) => {
|
|
let prevValue = INITIAL_VALUE;
|
|
return (value: any /** TODO #9100 */) => {
|
|
if (this.inputChanges !== null) {
|
|
this.inputChangeCount++;
|
|
this.inputChanges[prop] = new SimpleChange(
|
|
value, prevValue === INITIAL_VALUE ? value : prevValue,
|
|
prevValue === INITIAL_VALUE);
|
|
prevValue = value;
|
|
}
|
|
this.component[prop] = value;
|
|
};
|
|
})(input.prop);
|
|
attrs.$observe(input.attr, observeFn);
|
|
|
|
} else if (attrs.hasOwnProperty(input.bindAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[input.bindAttr];
|
|
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[input.bracketAttr];
|
|
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[input.bindonAttr];
|
|
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[input.bracketParenAttr];
|
|
}
|
|
if (expr != null) {
|
|
const watchFn =
|
|
((prop: any /** TODO #9100 */) => (
|
|
value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => {
|
|
if (this.inputChanges != null) {
|
|
this.inputChangeCount++;
|
|
this.inputChanges[prop] = new SimpleChange(prevValue, value, prevValue === value);
|
|
}
|
|
this.component[prop] = value;
|
|
})(input.prop);
|
|
this.componentScope.$watch(expr, watchFn);
|
|
}
|
|
}
|
|
|
|
const prototype = this.info.component.prototype;
|
|
if (prototype && (<OnChanges>prototype).ngOnChanges) {
|
|
// Detect: OnChanges interface
|
|
this.inputChanges = {};
|
|
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
|
const inputChanges = this.inputChanges;
|
|
this.inputChanges = {};
|
|
(<OnChanges>this.component).ngOnChanges(inputChanges);
|
|
});
|
|
}
|
|
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 || [];
|
|
for (let j = 0; j < outputs.length; j++) {
|
|
const output = new PropertyBinding(outputs[j]);
|
|
let expr: any /** TODO #9100 */ = null;
|
|
let assignExpr = false;
|
|
|
|
const bindonAttr =
|
|
output.bindonAttr ? output.bindonAttr.substring(0, output.bindonAttr.length - 6) : null;
|
|
const bracketParenAttr = output.bracketParenAttr ?
|
|
`[(${output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8)})]` :
|
|
null;
|
|
|
|
if (attrs.hasOwnProperty(output.onAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[output.onAttr];
|
|
} else if (attrs.hasOwnProperty(output.parenAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[output.parenAttr];
|
|
} else if (attrs.hasOwnProperty(bindonAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[bindonAttr];
|
|
assignExpr = true;
|
|
} else if (attrs.hasOwnProperty(bracketParenAttr)) {
|
|
expr = (attrs as any /** TODO #9100 */)[bracketParenAttr];
|
|
assignExpr = true;
|
|
}
|
|
|
|
if (expr != null && assignExpr != null) {
|
|
const getter = this.parse(expr);
|
|
const setter = getter.assign;
|
|
if (assignExpr && !setter) {
|
|
throw new Error(`Expression '${expr}' is not assignable!`);
|
|
}
|
|
const emitter = this.component[output.prop] as EventEmitter<any>;
|
|
if (emitter) {
|
|
emitter.subscribe({
|
|
next: assignExpr ?
|
|
((setter: any) => (v: any /** TODO #9100 */) => setter(this.scope, v))(setter) :
|
|
((getter: any) => (v: any /** TODO #9100 */) =>
|
|
getter(this.scope, {$event: v}))(getter)
|
|
});
|
|
} else {
|
|
throw new Error(
|
|
`Missing emitter '${output.prop}' on component '${this.info.component}'!`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
registerCleanup() {
|
|
this.element.bind('$destroy', () => {
|
|
this.componentScope.$destroy();
|
|
this.componentRef.destroy();
|
|
});
|
|
}
|
|
}
|