refactor: move angular source to /packages rather than modules/@angular

This commit is contained in:
Jason Aden
2017-03-02 10:48:42 -08:00
parent 5ad5301a3e
commit 3e51a19983
1051 changed files with 18 additions and 18 deletions

View File

@ -0,0 +1,150 @@
/**
* @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 {AnimateTimings, AnimationMetadataType, animate as _animate, group as _group, keyframes as _keyframes, sequence as _sequence, state as _state, style as _style, transition as _transition, trigger as _trigger} from './dsl';
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export const AUTO_STYLE = '*';
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationMetadata { type: AnimationMetadataType; }
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationTriggerMetadata {
name: string;
definitions: AnimationMetadata[];
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationStateMetadata extends AnimationMetadata {
name: string;
styles: AnimationStyleMetadata;
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationTransitionMetadata extends AnimationMetadata {
expr: string|((fromState: string, toState: string) => boolean);
animation: AnimationMetadata|AnimationMetadata[];
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationKeyframesSequenceMetadata extends AnimationMetadata {
steps: AnimationStyleMetadata[];
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationStyleMetadata extends AnimationMetadata {
styles: {[key: string]: string | number}|{[key: string]: string | number}[];
offset?: number;
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationAnimateMetadata extends AnimationMetadata {
timings: string|number|AnimateTimings;
styles: AnimationStyleMetadata|AnimationKeyframesSequenceMetadata;
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationSequenceMetadata extends AnimationMetadata { steps: AnimationMetadata[]; }
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export interface AnimationGroupMetadata extends AnimationMetadata { steps: AnimationMetadata[]; }
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function trigger(name: string, definitions: AnimationMetadata[]): AnimationTriggerMetadata {
return _trigger(name, definitions);
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function animate(
timings: string | number, styles: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata =
null): AnimationAnimateMetadata {
return _animate(timings, styles);
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function group(steps: AnimationMetadata[]): AnimationGroupMetadata {
return _group(steps);
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function sequence(steps: AnimationMetadata[]): AnimationSequenceMetadata {
return _sequence(steps);
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function style(
tokens: {[key: string]: string | number} |
Array<{[key: string]: string | number}>): AnimationStyleMetadata {
return _style(tokens);
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function state(name: string, styles: AnimationStyleMetadata): AnimationStateMetadata {
return _state(name, styles);
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSequenceMetadata {
return _keyframes(steps);
}
/**
* @deprecated This symbol has moved. Please Import from @angular/animations instead!
*/
export function transition(
stateChangeExpr: string | ((fromState: string, toState: string) => boolean),
steps: AnimationMetadata | AnimationMetadata[]): AnimationTransitionMetadata {
return _transition(stateChangeExpr, steps);
}
/**
* @deprecated This has been renamed to `AnimationEvent`. Please import it from @angular/animations.
*/
export interface AnimationTransitionEvent {
fromState: string;
toState: string;
totalTime: number;
phaseName: string;
element: any;
triggerName: string;
}

View File

@ -0,0 +1 @@
../../../animations/src/animation_metadata.ts

View File

@ -0,0 +1,49 @@
/**
* @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 {isPromise} from '../src/util/lang';
import {Inject, Injectable, InjectionToken, Optional} from './di';
/**
* A function that will be executed when an application is initialized.
* @experimental
*/
export const APP_INITIALIZER = new InjectionToken<Array<() => void>>('Application Initializer');
/**
* A class that reflects the state of running {@link APP_INITIALIZER}s.
*
* @experimental
*/
@Injectable()
export class ApplicationInitStatus {
private _donePromise: Promise<any>;
private _done = false;
constructor(@Inject(APP_INITIALIZER) @Optional() appInits: (() => any)[]) {
const asyncInitPromises: Promise<any>[] = [];
if (appInits) {
for (let i = 0; i < appInits.length; i++) {
const initResult = appInits[i]();
if (isPromise(initResult)) {
asyncInitPromises.push(initResult);
}
}
}
this._donePromise = Promise.all(asyncInitPromises).then(() => { this._done = true; });
if (asyncInitPromises.length === 0) {
this._done = true;
}
}
get done(): boolean { return this._done; }
get donePromise(): Promise<any> { return this._donePromise; }
}

View File

@ -0,0 +1,59 @@
/**
* @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 {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
import {ApplicationRef, ApplicationRef_} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
import {Inject, Optional, SkipSelf} from './di/metadata';
import {LOCALE_ID} from './i18n/tokens';
import {Compiler} from './linker/compiler';
import {NgModule} from './metadata';
import {initServicesIfNeeded} from './view/index';
export function _iterableDiffersFactory() {
return defaultIterableDiffers;
}
export function _keyValueDiffersFactory() {
return defaultKeyValueDiffers;
}
export function _localeFactory(locale?: string): string {
return locale || 'en-US';
}
export function _initViewEngine() {
initServicesIfNeeded();
}
/**
* This module includes the providers of @angular/core that are needed
* to bootstrap components via `ApplicationRef`.
*
* @experimental
*/
@NgModule({
providers: [
ApplicationRef_,
{provide: ApplicationRef, useExisting: ApplicationRef_},
ApplicationInitStatus,
Compiler,
APP_ID_RANDOM_PROVIDER,
{provide: IterableDiffers, useFactory: _iterableDiffersFactory},
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory},
{
provide: LOCALE_ID,
useFactory: _localeFactory,
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
},
{provide: APP_INITIALIZER, useValue: _initViewEngine, multi: true},
]
})
export class ApplicationModule {
}

View File

@ -0,0 +1,573 @@
/**
* @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 {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import {Subject} from 'rxjs/Subject';
import {Subscription} from 'rxjs/Subscription';
import {merge} from 'rxjs/observable/merge';
import {share} from 'rxjs/operator/share';
import {ErrorHandler} from '../src/error_handler';
import {scheduleMicroTask, stringify} from '../src/util';
import {isPromise} from '../src/util/lang';
import {ApplicationInitStatus} from './application_init';
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
import {Console} from './console';
import {Injectable, InjectionToken, Injector, Optional, Provider, ReflectiveInjector} from './di';
import {CompilerFactory, CompilerOptions} from './linker/compiler';
import {ComponentFactory, ComponentRef} from './linker/component_factory';
import {ComponentFactoryResolver} from './linker/component_factory_resolver';
import {NgModuleFactory, NgModuleInjector, NgModuleRef} from './linker/ng_module_factory';
import {InternalViewRef, ViewRef} from './linker/view_ref';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
import {Testability, TestabilityRegistry} from './testability/testability';
import {Type} from './type';
import {NgZone} from './zone/ng_zone';
let _devMode: boolean = true;
let _runModeLocked: boolean = false;
let _platform: PlatformRef;
export const ALLOW_MULTIPLE_PLATFORMS = new InjectionToken<boolean>('AllowMultipleToken');
/**
* Disable Angular's development mode, which turns off assertions and other
* checks within the framework.
*
* One important assertion this disables verifies that a change detection pass
* does not result in additional changes to any bindings (also known as
* unidirectional data flow).
*
* @stable
*/
export function enableProdMode(): void {
if (_runModeLocked) {
throw new Error('Cannot enable prod mode after platform setup.');
}
_devMode = false;
}
/**
* Returns whether Angular is in development mode. After called once,
* the value is locked and won't change any more.
*
* By default, this is true, unless a user calls `enableProdMode` before calling this.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function isDevMode(): boolean {
_runModeLocked = true;
return _devMode;
}
/**
* A token for third-party components that can register themselves with NgProbe.
*
* @experimental
*/
export class NgProbeToken {
constructor(public name: string, public token: any) {}
}
/**
* Creates a platform.
* Platforms have to be eagerly created via this function.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function createPlatform(injector: Injector): PlatformRef {
if (_platform && !_platform.destroyed &&
!_platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
throw new Error(
'There can be only one platform. Destroy the previous one to create a new one.');
}
_platform = injector.get(PlatformRef);
const inits = injector.get(PLATFORM_INITIALIZER, null);
if (inits) inits.forEach(init => init());
return _platform;
}
/**
* Creates a factory for a platform
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function createPlatformFactory(
parentPlatformFactory: (extraProviders?: Provider[]) => PlatformRef, name: string,
providers: Provider[] = []): (extraProviders?: Provider[]) => PlatformRef {
const marker = new InjectionToken(`Platform: ${name}`);
return (extraProviders: Provider[] = []) => {
let platform = getPlatform();
if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
if (parentPlatformFactory) {
parentPlatformFactory(
providers.concat(extraProviders).concat({provide: marker, useValue: true}));
} else {
createPlatform(ReflectiveInjector.resolveAndCreate(
providers.concat(extraProviders).concat({provide: marker, useValue: true})));
}
}
return assertPlatform(marker);
};
}
/**
* Checks that there currently is a platform which contains the given token as a provider.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function assertPlatform(requiredToken: any): PlatformRef {
const platform = getPlatform();
if (!platform) {
throw new Error('No platform exists!');
}
if (!platform.injector.get(requiredToken, null)) {
throw new Error(
'A platform with a different configuration has been created. Please destroy it first.');
}
return platform;
}
/**
* Destroy the existing platform.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function destroyPlatform(): void {
if (_platform && !_platform.destroyed) {
_platform.destroy();
}
}
/**
* Returns the current platform.
*
* @experimental APIs related to application bootstrap are currently under review.
*/
export function getPlatform(): PlatformRef {
return _platform && !_platform.destroyed ? _platform : null;
}
/**
* The Angular platform is the entry point for Angular on a web page. Each page
* has exactly one platform, and services (such as reflection) which are common
* to every Angular application running on the page are bound in its scope.
*
* A page's platform is initialized implicitly when {@link bootstrap}() is called, or
* explicitly by calling {@link createPlatform}().
*
* @stable
*/
export abstract class PlatformRef {
/**
* Creates an instance of an `@NgModule` for the given platform
* for offline compilation.
*
* ## Simple Example
*
* ```typescript
* my_module.ts:
*
* @NgModule({
* imports: [BrowserModule]
* })
* class MyModule {}
*
* main.ts:
* import {MyModuleNgFactory} from './my_module.ngfactory';
* import {platformBrowser} from '@angular/platform-browser';
*
* let moduleRef = platformBrowser().bootstrapModuleFactory(MyModuleNgFactory);
* ```
*
* @experimental APIs related to application bootstrap are currently under review.
*/
abstract bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>>;
/**
* Creates an instance of an `@NgModule` for a given platform using the given runtime compiler.
*
* ## Simple Example
*
* ```typescript
* @NgModule({
* imports: [BrowserModule]
* })
* class MyModule {}
*
* let moduleRef = platformBrowser().bootstrapModule(MyModule);
* ```
* @stable
*/
abstract bootstrapModule<M>(
moduleType: Type<M>,
compilerOptions?: CompilerOptions|CompilerOptions[]): Promise<NgModuleRef<M>>;
/**
* Register a listener to be called when the platform is disposed.
*/
abstract onDestroy(callback: () => void): void;
/**
* Retrieve the platform {@link Injector}, which is the parent injector for
* every Angular application on the page and provides singleton providers.
*/
abstract get injector(): Injector;
/**
* Destroy the Angular platform and all Angular applications on the page.
*/
abstract destroy(): void;
abstract get destroyed(): boolean;
}
function _callAndReportToErrorHandler(errorHandler: ErrorHandler, callback: () => any): any {
try {
const result = callback();
if (isPromise(result)) {
return result.catch((e: any) => {
errorHandler.handleError(e);
// rethrow as the exception handler might not do it
throw e;
});
}
return result;
} catch (e) {
errorHandler.handleError(e);
// rethrow as the exception handler might not do it
throw e;
}
}
/**
* workaround https://github.com/angular/tsickle/issues/350
* @suppress {checkTypes}
*/
@Injectable()
export class PlatformRef_ extends PlatformRef {
private _modules: NgModuleRef<any>[] = [];
private _destroyListeners: Function[] = [];
private _destroyed: boolean = false;
constructor(private _injector: Injector) { super(); }
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
get injector(): Injector { return this._injector; }
get destroyed() { return this._destroyed; }
destroy() {
if (this._destroyed) {
throw new Error('The platform has already been destroyed!');
}
this._modules.slice().forEach(module => module.destroy());
this._destroyListeners.forEach(listener => listener());
this._destroyed = true;
}
bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>): Promise<NgModuleRef<M>> {
return this._bootstrapModuleFactoryWithZone(moduleFactory, null);
}
private _bootstrapModuleFactoryWithZone<M>(moduleFactory: NgModuleFactory<M>, ngZone: NgZone):
Promise<NgModuleRef<M>> {
// Note: We need to create the NgZone _before_ we instantiate the module,
// as instantiating the module creates some providers eagerly.
// So we create a mini parent injector that just contains the new NgZone and
// pass that as parent to the NgModuleFactory.
if (!ngZone) ngZone = new NgZone({enableLongStackTrace: isDevMode()});
// Attention: Don't use ApplicationRef.run here,
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
return ngZone.run(() => {
const ngZoneInjector =
ReflectiveInjector.resolveAndCreate([{provide: NgZone, useValue: ngZone}], this.injector);
const moduleRef = <NgModuleInjector<M>>moduleFactory.create(ngZoneInjector);
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) {
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
}
moduleRef.onDestroy(() => remove(this._modules, moduleRef));
ngZone.onError.subscribe({next: (error: any) => { exceptionHandler.handleError(error); }});
return _callAndReportToErrorHandler(exceptionHandler, () => {
const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
return initStatus.donePromise.then(() => {
this._moduleDoBootstrap(moduleRef);
return moduleRef;
});
});
});
}
bootstrapModule<M>(moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = []):
Promise<NgModuleRef<M>> {
return this._bootstrapModuleWithZone(moduleType, compilerOptions, null);
}
private _bootstrapModuleWithZone<M>(
moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = [],
ngZone: NgZone = null): Promise<NgModuleRef<M>> {
const compilerFactory: CompilerFactory = this.injector.get(CompilerFactory);
const compiler = compilerFactory.createCompiler(
Array.isArray(compilerOptions) ? compilerOptions : [compilerOptions]);
return compiler.compileModuleAsync(moduleType)
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
}
private _moduleDoBootstrap(moduleRef: NgModuleInjector<any>): void {
const appRef = moduleRef.injector.get(ApplicationRef);
if (moduleRef.bootstrapFactories.length > 0) {
moduleRef.bootstrapFactories.forEach((compFactory) => appRef.bootstrap(compFactory));
} else if (moduleRef.instance.ngDoBootstrap) {
moduleRef.instance.ngDoBootstrap(appRef);
} else {
throw new Error(
`The module ${stringify(moduleRef.instance.constructor)} was bootstrapped, but it does not declare "@NgModule.bootstrap" components nor a "ngDoBootstrap" method. ` +
`Please define one of these.`);
}
this._modules.push(moduleRef);
}
}
/**
* A reference to an Angular application running on a page.
*
* For more about Angular applications, see the documentation for {@link bootstrap}.
*
* @stable
*/
export abstract class ApplicationRef {
/**
* Bootstrap a new component at the root level of the application.
*
* ### Bootstrap process
*
* When bootstrapping a new root component into an application, Angular mounts the
* specified application component onto DOM elements identified by the [componentType]'s
* selector and kicks off automatic change detection to finish initializing the component.
*
* ### Example
* {@example core/ts/platform/platform.ts region='longform'}
*/
abstract bootstrap<C>(componentFactory: ComponentFactory<C>|Type<C>): ComponentRef<C>;
/**
* Invoke this method to explicitly process change detection and its side-effects.
*
* In development mode, `tick()` also performs a second change detection cycle to ensure that no
* further changes are detected. If additional changes are picked up during this second cycle,
* bindings in the app have side-effects that cannot be resolved in a single change detection
* pass.
* In this case, Angular throws an error, since an Angular application can only have one change
* detection pass during which all change detection must complete.
*/
abstract tick(): void;
/**
* Get a list of component types registered to this application.
* This list is populated even before the component is created.
*/
abstract get componentTypes(): Type<any>[];
/**
* Get a list of components registered to this application.
*/
abstract get components(): ComponentRef<any>[];
/**
* Attaches a view so that it will be dirty checked.
* The view will be automatically detached when it is destroyed.
* This will throw if the view is already attached to a ViewContainer.
*/
abstract attachView(view: ViewRef): void;
/**
* Detaches a view from dirty checking again.
*/
abstract detachView(view: ViewRef): void;
/**
* Returns the number of attached views.
*/
abstract get viewCount(): number;
/**
* Returns an Observable that indicates when the application is stable or unstable.
*/
abstract get isStable(): Observable<boolean>;
}
/**
* workaround https://github.com/angular/tsickle/issues/350
* @suppress {checkTypes}
*/
@Injectable()
export class ApplicationRef_ extends ApplicationRef {
/** @internal */
static _tickScope: WtfScopeFn = wtfCreateScope('ApplicationRef#tick()');
private _bootstrapListeners: ((compRef: ComponentRef<any>) => void)[] = [];
private _rootComponents: ComponentRef<any>[] = [];
private _rootComponentTypes: Type<any>[] = [];
private _views: InternalViewRef[] = [];
private _runningTick: boolean = false;
private _enforceNoNewChanges: boolean = false;
private _isStable: Observable<boolean>;
private _stable = true;
constructor(
private _zone: NgZone, private _console: Console, private _injector: Injector,
private _exceptionHandler: ErrorHandler,
private _componentFactoryResolver: ComponentFactoryResolver,
private _initStatus: ApplicationInitStatus) {
super();
this._enforceNoNewChanges = isDevMode();
this._zone.onMicrotaskEmpty.subscribe(
{next: () => { this._zone.run(() => { this.tick(); }); }});
const isCurrentlyStable = new Observable<boolean>((observer: Observer<boolean>) => {
this._stable = this._zone.isStable && !this._zone.hasPendingMacrotasks &&
!this._zone.hasPendingMicrotasks;
this._zone.runOutsideAngular(() => {
observer.next(this._stable);
observer.complete();
});
});
const isStable = new Observable<boolean>((observer: Observer<boolean>) => {
const stableSub: Subscription = this._zone.onStable.subscribe(() => {
NgZone.assertNotInAngularZone();
// Check whether there are no pending macro/micro tasks in the next tick
// to allow for NgZone to update the state.
scheduleMicroTask(() => {
if (!this._stable && !this._zone.hasPendingMacrotasks &&
!this._zone.hasPendingMicrotasks) {
this._stable = true;
observer.next(true);
}
});
});
const unstableSub: Subscription = this._zone.onUnstable.subscribe(() => {
NgZone.assertInAngularZone();
if (this._stable) {
this._stable = false;
this._zone.runOutsideAngular(() => { observer.next(false); });
}
});
return () => {
stableSub.unsubscribe();
unstableSub.unsubscribe();
};
});
this._isStable = merge(isCurrentlyStable, share.call(isStable));
}
attachView(viewRef: ViewRef): void {
const view = (viewRef as InternalViewRef);
this._views.push(view);
view.attachToAppRef(this);
}
detachView(viewRef: ViewRef): void {
const view = (viewRef as InternalViewRef);
remove(this._views, view);
view.detachFromAppRef();
}
bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>): ComponentRef<C> {
if (!this._initStatus.done) {
throw new Error(
'Cannot bootstrap as there are still asynchronous initializers running. Bootstrap components in the `ngDoBootstrap` method of the root module.');
}
let componentFactory: ComponentFactory<C>;
if (componentOrFactory instanceof ComponentFactory) {
componentFactory = componentOrFactory;
} else {
componentFactory = this._componentFactoryResolver.resolveComponentFactory(componentOrFactory);
}
this._rootComponentTypes.push(componentFactory.componentType);
const compRef = componentFactory.create(this._injector, [], componentFactory.selector);
compRef.onDestroy(() => { this._unloadComponent(compRef); });
const testability = compRef.injector.get(Testability, null);
if (testability) {
compRef.injector.get(TestabilityRegistry)
.registerApplication(compRef.location.nativeElement, testability);
}
this._loadComponent(compRef);
if (isDevMode()) {
this._console.log(
`Angular is running in the development mode. Call enableProdMode() to enable the production mode.`);
}
return compRef;
}
private _loadComponent(componentRef: ComponentRef<any>): void {
this.attachView(componentRef.hostView);
this.tick();
this._rootComponents.push(componentRef);
// Get the listeners lazily to prevent DI cycles.
const listeners =
this._injector.get(APP_BOOTSTRAP_LISTENER, []).concat(this._bootstrapListeners);
listeners.forEach((listener) => listener(componentRef));
}
private _unloadComponent(componentRef: ComponentRef<any>): void {
this.detachView(componentRef.hostView);
remove(this._rootComponents, componentRef);
}
tick(): void {
if (this._runningTick) {
throw new Error('ApplicationRef.tick is called recursively');
}
const scope = ApplicationRef_._tickScope();
try {
this._runningTick = true;
this._views.forEach((view) => view.detectChanges());
if (this._enforceNoNewChanges) {
this._views.forEach((view) => view.checkNoChanges());
}
} finally {
this._runningTick = false;
wtfLeave(scope);
}
}
ngOnDestroy() {
// TODO(alxhub): Dispose of the NgZone.
this._views.slice().forEach((view) => view.destroy());
}
get viewCount() { return this._views.length; }
get componentTypes(): Type<any>[] { return this._rootComponentTypes; }
get components(): ComponentRef<any>[] { return this._rootComponents; }
get isStable(): Observable<boolean> { return this._isStable; }
}
function remove<T>(list: T[], el: T): void {
const index = list.indexOf(el);
if (index > -1) {
list.splice(index, 1);
}
}

View File

@ -0,0 +1,70 @@
/**
* @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 {InjectionToken} from './di';
import {ComponentRef} from './linker/component_factory';
/**
* A DI Token representing a unique string id assigned to the application by Angular and used
* primarily for prefixing application attributes and CSS styles when
* {@link ViewEncapsulation#Emulated} is being used.
*
* If you need to avoid randomly generated value to be used as an application id, you can provide
* a custom value via a DI provider <!-- TODO: provider --> configuring the root {@link Injector}
* using this token.
* @experimental
*/
export const APP_ID = new InjectionToken<string>('AppId');
export function _appIdRandomProviderFactory() {
return `${_randomChar()}${_randomChar()}${_randomChar()}`;
}
/**
* Providers that will generate a random APP_ID_TOKEN.
* @experimental
*/
export const APP_ID_RANDOM_PROVIDER = {
provide: APP_ID,
useFactory: _appIdRandomProviderFactory,
deps: <any[]>[],
};
function _randomChar(): string {
return String.fromCharCode(97 + Math.floor(Math.random() * 25));
}
/**
* A function that will be executed when a platform is initialized.
* @experimental
*/
export const PLATFORM_INITIALIZER = new InjectionToken<Array<() => void>>('Platform Initializer');
/**
* A token that indicates an opaque platform id.
* @experimental
*/
export const PLATFORM_ID = new InjectionToken<Object>('Platform ID');
/**
* All callbacks provided via this token will be called for every component that is bootstrapped.
* Signature of the callback:
*
* `(componentRef: ComponentRef) => void`.
*
* @experimental
*/
export const APP_BOOTSTRAP_LISTENER =
new InjectionToken<Array<(compRef: ComponentRef<any>) => void>>('appBootstrapListener');
/**
* A token which indicates the root directory of the application
* @experimental
*/
export const PACKAGE_ROOT_URL = new InjectionToken<string>('Application Packages Root URL');

View File

@ -0,0 +1,15 @@
/**
* @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
*/
/**
* @module
* @description
* Change detection enables data binding in Angular.
*/
export {ChangeDetectionStrategy, ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, IterableDiffers, KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers, NgIterable, PipeTransform, SimpleChange, SimpleChanges, TrackByFn, TrackByFunction, WrappedValue} from './change_detection/change_detection';

View File

@ -0,0 +1,39 @@
/**
* @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 {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
import {IterableDifferFactory, IterableDiffers} from './differs/iterable_differs';
import {KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs';
export {SimpleChanges} from '../metadata/lifecycle_hooks';
export {SimpleChange, ValueUnwrapper, WrappedValue, devModeEqual} from './change_detection_util';
export {ChangeDetectorRef} from './change_detector_ref';
export {ChangeDetectionStrategy, ChangeDetectorStatus, isDefaultChangeDetectionStrategy} from './constants';
export {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
export {DefaultIterableDiffer} from './differs/default_iterable_differ';
export {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
export {CollectionChangeRecord, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, IterableDiffers, NgIterable, TrackByFn, TrackByFunction} from './differs/iterable_differs';
export {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs';
export {PipeTransform} from './pipe_transform';
/**
* Structural diffing for `Object`s and `Map`s.
*/
const keyValDiff: KeyValueDifferFactory[] = [new DefaultKeyValueDifferFactory()];
/**
* Structural diffing for `Iterable` types such as `Array`s.
*/
const iterableDiff: IterableDifferFactory[] = [new DefaultIterableDifferFactory()];
export const defaultIterableDiffers = new IterableDiffers(iterableDiff);
export const defaultKeyValueDiffers = new KeyValueDiffers(keyValDiff);

View File

@ -0,0 +1,119 @@
/**
* @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 {getSymbolIterator, looseIdentical} from '../util';
export function devModeEqual(a: any, b: any): boolean {
const isListLikeIterableA = isListLikeIterable(a);
const isListLikeIterableB = isListLikeIterable(b);
if (isListLikeIterableA && isListLikeIterableB) {
return areIterablesEqual(a, b, devModeEqual);
} else {
const isAObject = a && (typeof a === 'object' || typeof a === 'function');
const isBObject = b && (typeof b === 'object' || typeof b === 'function');
if (!isListLikeIterableA && isAObject && !isListLikeIterableB && isBObject) {
return true;
} else {
return looseIdentical(a, b);
}
}
}
/**
* Indicates that the result of a {@link Pipe} transformation has changed even though the
* reference
* has not changed.
*
* The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
*
* Example:
*
* ```
* if (this._latestValue === this._latestReturnedValue) {
* return this._latestReturnedValue;
* } else {
* this._latestReturnedValue = this._latestValue;
* return WrappedValue.wrap(this._latestValue); // this will force update
* }
* ```
* @stable
*/
export class WrappedValue {
constructor(public wrapped: any) {}
static wrap(value: any): WrappedValue { return new WrappedValue(value); }
}
/**
* Helper class for unwrapping WrappedValue s
*/
export class ValueUnwrapper {
public hasWrappedValue = false;
unwrap(value: any): any {
if (value instanceof WrappedValue) {
this.hasWrappedValue = true;
return value.wrapped;
}
return value;
}
reset() { this.hasWrappedValue = false; }
}
/**
* Represents a basic change from a previous to a new value.
* @stable
*/
export class SimpleChange {
constructor(public previousValue: any, public currentValue: any, public firstChange: boolean) {}
/**
* Check whether the new value is the first value assigned.
*/
isFirstChange(): boolean { return this.firstChange; }
}
export function isListLikeIterable(obj: any): boolean {
if (!isJsObject(obj)) return false;
return Array.isArray(obj) ||
(!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
getSymbolIterator() in obj); // JS Iterable have a Symbol.iterator prop
}
export function areIterablesEqual(
a: any, b: any, comparator: (a: any, b: any) => boolean): boolean {
const iterator1 = a[getSymbolIterator()]();
const iterator2 = b[getSymbolIterator()]();
while (true) {
const item1 = iterator1.next();
const item2 = iterator2.next();
if (item1.done && item2.done) return true;
if (item1.done || item2.done) return false;
if (!comparator(item1.value, item2.value)) return false;
}
}
export function iterateListLike(obj: any, fn: (p: any) => any) {
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
fn(obj[i]);
}
} else {
const iterator = obj[getSymbolIterator()]();
let item: any;
while (!((item = iterator.next()).done)) {
fn(item.value);
}
}
}
export function isJsObject(o: any): boolean {
return o !== null && (typeof o === 'function' || typeof o === 'object');
}

View File

@ -0,0 +1,193 @@
/**
* @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
*/
/**
* @stable
*/
export abstract class ChangeDetectorRef {
/**
* Marks all {@link ChangeDetectionStrategy#OnPush} ancestors as to be checked.
*
* <!-- TODO: Add a link to a chapter on OnPush components -->
*
* ### Example ([live demo](http://plnkr.co/edit/GC512b?p=preview))
*
* ```typescript
* @Component({
* selector: 'cmp',
* changeDetection: ChangeDetectionStrategy.OnPush,
* template: `Number of ticks: {{numberOfTicks}}`
* })
* class Cmp {
* numberOfTicks = 0;
*
* constructor(ref: ChangeDetectorRef) {
* setInterval(() => {
* this.numberOfTicks ++
* // the following is required, otherwise the view will not be updated
* this.ref.markForCheck();
* }, 1000);
* }
* }
*
* @Component({
* selector: 'app',
* changeDetection: ChangeDetectionStrategy.OnPush,
* template: `
* <cmp><cmp>
* `,
* })
* class App {
* }
* ```
*/
abstract markForCheck(): void;
/**
* Detaches the change detector from the change detector tree.
*
* The detached change detector will not be checked until it is reattached.
*
* This can also be used in combination with {@link ChangeDetectorRef#detectChanges} to implement
* local change
* detection checks.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
*
* ### Example
*
* The following example defines a component with a large list of readonly data.
* Imagine the data changes constantly, many times per second. For performance reasons,
* we want to check and update the list every five seconds. We can do that by detaching
* the component's change detector and doing a local check every five seconds.
*
* ```typescript
* class DataProvider {
* // in a real application the returned data will be different every time
* get data() {
* return [1,2,3,4,5];
* }
* }
*
* @Component({
* selector: 'giant-list',
* template: `
* <li *ngFor="let d of dataProvider.data">Data {{d}}</lig>
* `,
* })
* class GiantList {
* constructor(private ref: ChangeDetectorRef, private dataProvider:DataProvider) {
* ref.detach();
* setInterval(() => {
* this.ref.detectChanges();
* }, 5000);
* }
* }
*
* @Component({
* selector: 'app',
* providers: [DataProvider],
* template: `
* <giant-list><giant-list>
* `,
* })
* class App {
* }
* ```
*/
abstract detach(): void;
/**
* Checks the change detector and its children.
*
* This can also be used in combination with {@link ChangeDetectorRef#detach} to implement local
* change detection
* checks.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
*
* ### Example
*
* The following example defines a component with a large list of readonly data.
* Imagine, the data changes constantly, many times per second. For performance reasons,
* we want to check and update the list every five seconds.
*
* We can do that by detaching the component's change detector and doing a local change detection
* check
* every five seconds.
*
* See {@link ChangeDetectorRef#detach} for more information.
*/
abstract detectChanges(): void;
/**
* Checks the change detector and its children, and throws if any changes are detected.
*
* This is used in development mode to verify that running change detection doesn't introduce
* other changes.
*/
abstract checkNoChanges(): void;
/**
* Reattach the change detector to the change detector tree.
*
* This also marks OnPush ancestors as to be checked. This reattached change detector will be
* checked during the next change detection run.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
*
* ### Example ([live demo](http://plnkr.co/edit/aUhZha?p=preview))
*
* The following example creates a component displaying `live` data. The component will detach
* its change detector from the main change detector tree when the component's live property
* is set to false.
*
* ```typescript
* class DataProvider {
* data = 1;
*
* constructor() {
* setInterval(() => {
* this.data = this.data * 2;
* }, 500);
* }
* }
*
* @Component({
* selector: 'live-data',
* inputs: ['live'],
* template: 'Data: {{dataProvider.data}}'
* })
* class LiveData {
* constructor(private ref: ChangeDetectorRef, private dataProvider:DataProvider) {}
*
* set live(value) {
* if (value)
* this.ref.reattach();
* else
* this.ref.detach();
* }
* }
*
* @Component({
* selector: 'app',
* providers: [DataProvider],
* template: `
* Live Update: <input type="checkbox" [(ngModel)]="live">
* <live-data [live]="live"><live-data>
* `,
* })
* class App {
* live = true;
* }
* ```
*/
abstract reattach(): void;
}

View File

@ -0,0 +1,72 @@
/**
* @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
*/
/**
* Describes within the change detector which strategy will be used the next time change
* detection is triggered.
* @stable
*/
export enum ChangeDetectionStrategy {
/**
* `OnPush` means that the change detector's mode will be set to `CheckOnce` during hydration.
*/
OnPush,
/**
* `Default` means that the change detector's mode will be set to `CheckAlways` during hydration.
*/
Default,
}
/**
* Describes the status of the detector.
*/
export enum ChangeDetectorStatus {
/**
* `CheckOnce` means that after calling detectChanges the mode of the change detector
* will become `Checked`.
*/
CheckOnce,
/**
* `Checked` means that the change detector should be skipped until its mode changes to
* `CheckOnce`.
*/
Checked,
/**
* `CheckAlways` means that after calling detectChanges the mode of the change detector
* will remain `CheckAlways`.
*/
CheckAlways,
/**
* `Detached` means that the change detector sub tree is not a part of the main tree and
* should be skipped.
*/
Detached,
/**
* `Errored` means that the change detector encountered an error checking a binding
* or calling a directive lifecycle method and is now in an inconsistent state. Change
* detectors in this state will no longer detect changes.
*/
Errored,
/**
* `Destroyed` means that the change detector is destroyed.
*/
Destroyed,
}
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: ChangeDetectionStrategy):
boolean {
return changeDetectionStrategy == null ||
changeDetectionStrategy === ChangeDetectionStrategy.Default;
}

View File

@ -0,0 +1,764 @@
/**
* @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 {looseIdentical, stringify} from '../../util';
import {isListLikeIterable, iterateListLike} from '../change_detection_util';
import {ChangeDetectorRef} from '../change_detector_ref';
import {IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, NgIterable, TrackByFunction} from './iterable_differs';
export class DefaultIterableDifferFactory implements IterableDifferFactory {
constructor() {}
supports(obj: Object): boolean { return isListLikeIterable(obj); }
create<V>(trackByFn?: TrackByFunction<any>): DefaultIterableDiffer<V>;
/**
* @deprecated v4.0.0 - ChangeDetectorRef is not used and is no longer a parameter
*/
create<V>(
cdRefOrTrackBy?: ChangeDetectorRef|TrackByFunction<any>,
trackByFn?: TrackByFunction<any>): DefaultIterableDiffer<V> {
return new DefaultIterableDiffer<V>(trackByFn || <TrackByFunction<any>>cdRefOrTrackBy);
}
}
const trackByIdentity = (index: number, item: any) => item;
/**
* @deprecated v4.0.0 - Should not be part of public API.
*/
export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChanges<V> {
private _length: number = null;
private _collection: NgIterable<V> = null;
// Keeps track of the used records at any point in time (during & across `_check()` calls)
private _linkedRecords: _DuplicateMap<V> = null;
// Keeps track of the removed records at any point in time during `_check()` calls.
private _unlinkedRecords: _DuplicateMap<V> = null;
private _previousItHead: IterableChangeRecord_<V> = null;
private _itHead: IterableChangeRecord_<V> = null;
private _itTail: IterableChangeRecord_<V> = null;
private _additionsHead: IterableChangeRecord_<V> = null;
private _additionsTail: IterableChangeRecord_<V> = null;
private _movesHead: IterableChangeRecord_<V> = null;
private _movesTail: IterableChangeRecord_<V> = null;
private _removalsHead: IterableChangeRecord_<V> = null;
private _removalsTail: IterableChangeRecord_<V> = null;
// Keeps track of records where custom track by is the same, but item identity has changed
private _identityChangesHead: IterableChangeRecord_<V> = null;
private _identityChangesTail: IterableChangeRecord_<V> = null;
constructor(private _trackByFn?: TrackByFunction<V>) {
this._trackByFn = this._trackByFn || trackByIdentity;
}
get collection() { return this._collection; }
get length(): number { return this._length; }
forEachItem(fn: (record: IterableChangeRecord_<V>) => void) {
let record: IterableChangeRecord_<V>;
for (record = this._itHead; record !== null; record = record._next) {
fn(record);
}
}
forEachOperation(
fn: (item: IterableChangeRecord_<V>, previousIndex: number, currentIndex: number) => void) {
let nextIt = this._itHead;
let nextRemove = this._removalsHead;
let addRemoveOffset = 0;
let moveOffsets: number[] = null;
while (nextIt || nextRemove) {
// Figure out which is the next record to process
// Order: remove, add, move
const record = !nextRemove ||
nextIt &&
nextIt.currentIndex < getPreviousIndex(nextRemove, addRemoveOffset, moveOffsets) ?
nextIt :
nextRemove;
const adjPreviousIndex = getPreviousIndex(record, addRemoveOffset, moveOffsets);
const currentIndex = record.currentIndex;
// consume the item, and adjust the addRemoveOffset and update moveDistance if necessary
if (record === nextRemove) {
addRemoveOffset--;
nextRemove = nextRemove._nextRemoved;
} else {
nextIt = nextIt._next;
if (record.previousIndex == null) {
addRemoveOffset++;
} else {
// INVARIANT: currentIndex < previousIndex
if (!moveOffsets) moveOffsets = [];
const localMovePreviousIndex = adjPreviousIndex - addRemoveOffset;
const localCurrentIndex = currentIndex - addRemoveOffset;
if (localMovePreviousIndex != localCurrentIndex) {
for (let i = 0; i < localMovePreviousIndex; i++) {
const offset = i < moveOffsets.length ? moveOffsets[i] : (moveOffsets[i] = 0);
const index = offset + i;
if (localCurrentIndex <= index && index < localMovePreviousIndex) {
moveOffsets[i] = offset + 1;
}
}
const previousIndex = record.previousIndex;
moveOffsets[previousIndex] = localCurrentIndex - localMovePreviousIndex;
}
}
}
if (adjPreviousIndex !== currentIndex) {
fn(record, adjPreviousIndex, currentIndex);
}
}
}
forEachPreviousItem(fn: (record: IterableChangeRecord_<V>) => void) {
let record: IterableChangeRecord_<V>;
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
fn(record);
}
}
forEachAddedItem(fn: (record: IterableChangeRecord_<V>) => void) {
let record: IterableChangeRecord_<V>;
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record);
}
}
forEachMovedItem(fn: (record: IterableChangeRecord_<V>) => void) {
let record: IterableChangeRecord_<V>;
for (record = this._movesHead; record !== null; record = record._nextMoved) {
fn(record);
}
}
forEachRemovedItem(fn: (record: IterableChangeRecord_<V>) => void) {
let record: IterableChangeRecord_<V>;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record);
}
}
forEachIdentityChange(fn: (record: IterableChangeRecord_<V>) => void) {
let record: IterableChangeRecord_<V>;
for (record = this._identityChangesHead; record !== null; record = record._nextIdentityChange) {
fn(record);
}
}
diff(collection: NgIterable<V>): DefaultIterableDiffer<V> {
if (collection == null) collection = [];
if (!isListLikeIterable(collection)) {
throw new Error(`Error trying to diff '${collection}'`);
}
if (this.check(collection)) {
return this;
} else {
return null;
}
}
onDestroy() {}
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
check(collection: NgIterable<V>): boolean {
this._reset();
let record: IterableChangeRecord_<V> = this._itHead;
let mayBeDirty: boolean = false;
let index: number;
let item: V;
let itemTrackBy: any;
if (Array.isArray(collection)) {
this._length = collection.length;
for (let index = 0; index < this._length; index++) {
item = collection[index];
itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true;
} else {
if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, itemTrackBy, index);
}
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
}
record = record._next;
}
} else {
index = 0;
iterateListLike(collection, (item: V) => {
itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true;
} else {
if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, itemTrackBy, index);
}
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
}
record = record._next;
index++;
});
this._length = index;
}
this._truncate(record);
this._collection = collection;
return this.isDirty;
}
/* CollectionChanges is considered dirty if it has any additions, moves, removals, or identity
* changes.
*/
get isDirty(): boolean {
return this._additionsHead !== null || this._movesHead !== null ||
this._removalsHead !== null || this._identityChangesHead !== null;
}
/**
* Reset the state of the change objects to show no changes. This means set previousKey to
* currentKey, and clear all of the queues (additions, moves, removals).
* Set the previousIndexes of moved and added items to their currentIndexes
* Reset the list of additions, moves and removals
*
* @internal
*/
_reset() {
if (this.isDirty) {
let record: IterableChangeRecord_<V>;
let nextRecord: IterableChangeRecord_<V>;
for (record = this._previousItHead = this._itHead; record !== null; record = record._next) {
record._nextPrevious = record._next;
}
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
record.previousIndex = record.currentIndex;
}
this._additionsHead = this._additionsTail = null;
for (record = this._movesHead; record !== null; record = nextRecord) {
record.previousIndex = record.currentIndex;
nextRecord = record._nextMoved;
}
this._movesHead = this._movesTail = null;
this._removalsHead = this._removalsTail = null;
this._identityChangesHead = this._identityChangesTail = null;
// todo(vicb) when assert gets supported
// assert(!this.isDirty);
}
}
/**
* This is the core function which handles differences between collections.
*
* - `record` is the record which we saw at this position last time. If null then it is a new
* item.
* - `item` is the current item in the collection
* - `index` is the position of the item in the collection
*
* @internal
*/
_mismatch(record: IterableChangeRecord_<V>, item: V, itemTrackBy: any, index: number):
IterableChangeRecord_<V> {
// The previous record after which we will append the current one.
let previousRecord: IterableChangeRecord_<V>;
if (record === null) {
previousRecord = this._itTail;
} else {
previousRecord = record._prev;
// Remove the record from the collection since we know it does not match the item.
this._remove(record);
}
// Attempt to see if we have seen the item before.
record = this._linkedRecords === null ? null : this._linkedRecords.get(itemTrackBy, index);
if (record !== null) {
// We have seen this before, we need to move it forward in the collection.
// But first we need to check if identity changed, so we can update in view if necessary
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
this._moveAfter(record, previousRecord, index);
} else {
// Never seen it, check evicted list.
record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy);
if (record !== null) {
// It is an item which we have evicted earlier: reinsert it back into the list.
// But first we need to check if identity changed, so we can update in view if necessary
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
this._reinsertAfter(record, previousRecord, index);
} else {
// It is a new item: add it.
record =
this._addAfter(new IterableChangeRecord_<V>(item, itemTrackBy), previousRecord, index);
}
}
return record;
}
/**
* This check is only needed if an array contains duplicates. (Short circuit of nothing dirty)
*
* Use case: `[a, a]` => `[b, a, a]`
*
* If we did not have this check then the insertion of `b` would:
* 1) evict first `a`
* 2) insert `b` at `0` index.
* 3) leave `a` at index `1` as is. <-- this is wrong!
* 3) reinsert `a` at index 2. <-- this is wrong!
*
* The correct behavior is:
* 1) evict first `a`
* 2) insert `b` at `0` index.
* 3) reinsert `a` at index 1.
* 3) move `a` at from `1` to `2`.
*
*
* Double check that we have not evicted a duplicate item. We need to check if the item type may
* have already been removed:
* The insertion of b will evict the first 'a'. If we don't reinsert it now it will be reinserted
* at the end. Which will show up as the two 'a's switching position. This is incorrect, since a
* better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a'
* at the end.
*
* @internal
*/
_verifyReinsertion(record: IterableChangeRecord_<V>, item: V, itemTrackBy: any, index: number):
IterableChangeRecord_<V> {
let reinsertRecord: IterableChangeRecord_<V> =
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy);
if (reinsertRecord !== null) {
record = this._reinsertAfter(reinsertRecord, record._prev, index);
} else if (record.currentIndex != index) {
record.currentIndex = index;
this._addToMoves(record, index);
}
return record;
}
/**
* Get rid of any excess {@link IterableChangeRecord_}s from the previous collection
*
* - `record` The first excess {@link IterableChangeRecord_}.
*
* @internal
*/
_truncate(record: IterableChangeRecord_<V>) {
// Anything after that needs to be removed;
while (record !== null) {
const nextRecord: IterableChangeRecord_<V> = record._next;
this._addToRemovals(this._unlink(record));
record = nextRecord;
}
if (this._unlinkedRecords !== null) {
this._unlinkedRecords.clear();
}
if (this._additionsTail !== null) {
this._additionsTail._nextAdded = null;
}
if (this._movesTail !== null) {
this._movesTail._nextMoved = null;
}
if (this._itTail !== null) {
this._itTail._next = null;
}
if (this._removalsTail !== null) {
this._removalsTail._nextRemoved = null;
}
if (this._identityChangesTail !== null) {
this._identityChangesTail._nextIdentityChange = null;
}
}
/** @internal */
_reinsertAfter(
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>,
index: number): IterableChangeRecord_<V> {
if (this._unlinkedRecords !== null) {
this._unlinkedRecords.remove(record);
}
const prev = record._prevRemoved;
const next = record._nextRemoved;
if (prev === null) {
this._removalsHead = next;
} else {
prev._nextRemoved = next;
}
if (next === null) {
this._removalsTail = prev;
} else {
next._prevRemoved = prev;
}
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
/** @internal */
_moveAfter(record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>, index: number):
IterableChangeRecord_<V> {
this._unlink(record);
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
/** @internal */
_addAfter(record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>, index: number):
IterableChangeRecord_<V> {
this._insertAfter(record, prevRecord, index);
if (this._additionsTail === null) {
// todo(vicb)
// assert(this._additionsHead === null);
this._additionsTail = this._additionsHead = record;
} else {
// todo(vicb)
// assert(_additionsTail._nextAdded === null);
// assert(record._nextAdded === null);
this._additionsTail = this._additionsTail._nextAdded = record;
}
return record;
}
/** @internal */
_insertAfter(
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>,
index: number): IterableChangeRecord_<V> {
// todo(vicb)
// assert(record != prevRecord);
// assert(record._next === null);
// assert(record._prev === null);
const next: IterableChangeRecord_<V> = prevRecord === null ? this._itHead : prevRecord._next;
// todo(vicb)
// assert(next != record);
// assert(prevRecord != record);
record._next = next;
record._prev = prevRecord;
if (next === null) {
this._itTail = record;
} else {
next._prev = record;
}
if (prevRecord === null) {
this._itHead = record;
} else {
prevRecord._next = record;
}
if (this._linkedRecords === null) {
this._linkedRecords = new _DuplicateMap<V>();
}
this._linkedRecords.put(record);
record.currentIndex = index;
return record;
}
/** @internal */
_remove(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
return this._addToRemovals(this._unlink(record));
}
/** @internal */
_unlink(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
if (this._linkedRecords !== null) {
this._linkedRecords.remove(record);
}
const prev = record._prev;
const next = record._next;
// todo(vicb)
// assert((record._prev = null) === null);
// assert((record._next = null) === null);
if (prev === null) {
this._itHead = next;
} else {
prev._next = next;
}
if (next === null) {
this._itTail = prev;
} else {
next._prev = prev;
}
return record;
}
/** @internal */
_addToMoves(record: IterableChangeRecord_<V>, toIndex: number): IterableChangeRecord_<V> {
// todo(vicb)
// assert(record._nextMoved === null);
if (record.previousIndex === toIndex) {
return record;
}
if (this._movesTail === null) {
// todo(vicb)
// assert(_movesHead === null);
this._movesTail = this._movesHead = record;
} else {
// todo(vicb)
// assert(_movesTail._nextMoved === null);
this._movesTail = this._movesTail._nextMoved = record;
}
return record;
}
private _addToRemovals(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
if (this._unlinkedRecords === null) {
this._unlinkedRecords = new _DuplicateMap<V>();
}
this._unlinkedRecords.put(record);
record.currentIndex = null;
record._nextRemoved = null;
if (this._removalsTail === null) {
// todo(vicb)
// assert(_removalsHead === null);
this._removalsTail = this._removalsHead = record;
record._prevRemoved = null;
} else {
// todo(vicb)
// assert(_removalsTail._nextRemoved === null);
// assert(record._nextRemoved === null);
record._prevRemoved = this._removalsTail;
this._removalsTail = this._removalsTail._nextRemoved = record;
}
return record;
}
/** @internal */
_addIdentityChange(record: IterableChangeRecord_<V>, item: V) {
record.item = item;
if (this._identityChangesTail === null) {
this._identityChangesTail = this._identityChangesHead = record;
} else {
this._identityChangesTail = this._identityChangesTail._nextIdentityChange = record;
}
return record;
}
toString(): string {
const list: IterableChangeRecord_<V>[] = [];
this.forEachItem((record: IterableChangeRecord_<V>) => list.push(record));
const previous: IterableChangeRecord_<V>[] = [];
this.forEachPreviousItem((record: IterableChangeRecord_<V>) => previous.push(record));
const additions: IterableChangeRecord_<V>[] = [];
this.forEachAddedItem((record: IterableChangeRecord_<V>) => additions.push(record));
const moves: IterableChangeRecord_<V>[] = [];
this.forEachMovedItem((record: IterableChangeRecord_<V>) => moves.push(record));
const removals: IterableChangeRecord_<V>[] = [];
this.forEachRemovedItem((record: IterableChangeRecord_<V>) => removals.push(record));
const identityChanges: IterableChangeRecord_<V>[] = [];
this.forEachIdentityChange((record: IterableChangeRecord_<V>) => identityChanges.push(record));
return 'collection: ' + list.join(', ') + '\n' +
'previous: ' + previous.join(', ') + '\n' +
'additions: ' + additions.join(', ') + '\n' +
'moves: ' + moves.join(', ') + '\n' +
'removals: ' + removals.join(', ') + '\n' +
'identityChanges: ' + identityChanges.join(', ') + '\n';
}
}
/**
* @stable
*/
export class IterableChangeRecord_<V> implements IterableChangeRecord<V> {
currentIndex: number = null;
previousIndex: number = null;
/** @internal */
_nextPrevious: IterableChangeRecord_<V> = null;
/** @internal */
_prev: IterableChangeRecord_<V> = null;
/** @internal */
_next: IterableChangeRecord_<V> = null;
/** @internal */
_prevDup: IterableChangeRecord_<V> = null;
/** @internal */
_nextDup: IterableChangeRecord_<V> = null;
/** @internal */
_prevRemoved: IterableChangeRecord_<V> = null;
/** @internal */
_nextRemoved: IterableChangeRecord_<V> = null;
/** @internal */
_nextAdded: IterableChangeRecord_<V> = null;
/** @internal */
_nextMoved: IterableChangeRecord_<V> = null;
/** @internal */
_nextIdentityChange: IterableChangeRecord_<V> = null;
constructor(public item: V, public trackById: any) {}
toString(): string {
return this.previousIndex === this.currentIndex ? stringify(this.item) :
stringify(this.item) + '[' +
stringify(this.previousIndex) + '->' + stringify(this.currentIndex) + ']';
}
}
// A linked list of CollectionChangeRecords with the same IterableChangeRecord_.item
class _DuplicateItemRecordList<V> {
/** @internal */
_head: IterableChangeRecord_<V> = null;
/** @internal */
_tail: IterableChangeRecord_<V> = null;
/**
* Append the record to the list of duplicates.
*
* Note: by design all records in the list of duplicates hold the same value in record.item.
*/
add(record: IterableChangeRecord_<V>): void {
if (this._head === null) {
this._head = this._tail = record;
record._nextDup = null;
record._prevDup = null;
} else {
// todo(vicb)
// assert(record.item == _head.item ||
// record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN);
this._tail._nextDup = record;
record._prevDup = this._tail;
record._nextDup = null;
this._tail = record;
}
}
// Returns a IterableChangeRecord_ having IterableChangeRecord_.trackById == trackById and
// IterableChangeRecord_.currentIndex >= afterIndex
get(trackById: any, afterIndex: number): IterableChangeRecord_<V> {
let record: IterableChangeRecord_<V>;
for (record = this._head; record !== null; record = record._nextDup) {
if ((afterIndex === null || afterIndex < record.currentIndex) &&
looseIdentical(record.trackById, trackById)) {
return record;
}
}
return null;
}
/**
* Remove one {@link IterableChangeRecord_} from the list of duplicates.
*
* Returns whether the list of duplicates is empty.
*/
remove(record: IterableChangeRecord_<V>): boolean {
// todo(vicb)
// assert(() {
// // verify that the record being removed is in the list.
// for (IterableChangeRecord_ cursor = _head; cursor != null; cursor = cursor._nextDup) {
// if (identical(cursor, record)) return true;
// }
// return false;
//});
const prev: IterableChangeRecord_<V> = record._prevDup;
const next: IterableChangeRecord_<V> = record._nextDup;
if (prev === null) {
this._head = next;
} else {
prev._nextDup = next;
}
if (next === null) {
this._tail = prev;
} else {
next._prevDup = prev;
}
return this._head === null;
}
}
class _DuplicateMap<V> {
map = new Map<any, _DuplicateItemRecordList<V>>();
put(record: IterableChangeRecord_<V>) {
const key = record.trackById;
let duplicates = this.map.get(key);
if (!duplicates) {
duplicates = new _DuplicateItemRecordList<V>();
this.map.set(key, duplicates);
}
duplicates.add(record);
}
/**
* Retrieve the `value` using key. Because the IterableChangeRecord_ value may be one which we
* have already iterated over, we use the afterIndex to pretend it is not there.
*
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
* have any more `a`s needs to return the last `a` not the first or second.
*/
get(trackById: any, afterIndex: number = null): IterableChangeRecord_<V> {
const key = trackById;
const recordList = this.map.get(key);
return recordList ? recordList.get(trackById, afterIndex) : null;
}
/**
* Removes a {@link IterableChangeRecord_} from the list of duplicates.
*
* The list of duplicates also is removed from the map if it gets empty.
*/
remove(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
const key = record.trackById;
const recordList: _DuplicateItemRecordList<V> = this.map.get(key);
// Remove the list of duplicates when it gets empty
if (recordList.remove(record)) {
this.map.delete(key);
}
return record;
}
get isEmpty(): boolean { return this.map.size === 0; }
clear() { this.map.clear(); }
toString(): string { return '_DuplicateMap(' + stringify(this.map) + ')'; }
}
function getPreviousIndex(item: any, addRemoveOffset: number, moveOffsets: number[]): number {
const previousIndex = item.previousIndex;
if (previousIndex === null) return previousIndex;
let moveOffset = 0;
if (moveOffsets && previousIndex < moveOffsets.length) {
moveOffset = moveOffsets[previousIndex];
}
return previousIndex + addRemoveOffset + moveOffset;
}

View File

@ -0,0 +1,322 @@
/**
* @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 {looseIdentical, stringify} from '../../util';
import {isJsObject} from '../change_detection_util';
import {ChangeDetectorRef} from '../change_detector_ref';
import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory} from './keyvalue_differs';
export class DefaultKeyValueDifferFactory<K, V> implements KeyValueDifferFactory {
constructor() {}
supports(obj: any): boolean { return obj instanceof Map || isJsObject(obj); }
create<K, V>(): DefaultKeyValueDiffer<K, V>;
/**
* @deprecated v4.0.0 - ChangeDetectorRef is not used and is no longer a parameter
*/
create<K, V>(cd?: ChangeDetectorRef): KeyValueDiffer<K, V> {
return new DefaultKeyValueDiffer<K, V>();
}
}
export class DefaultKeyValueDiffer<K, V> implements KeyValueDiffer<K, V>, KeyValueChanges<K, V> {
private _records: Map<K, V> = new Map<K, V>();
private _mapHead: KeyValueChangeRecord_<K, V> = null;
private _previousMapHead: KeyValueChangeRecord_<K, V> = null;
private _changesHead: KeyValueChangeRecord_<K, V> = null;
private _changesTail: KeyValueChangeRecord_<K, V> = null;
private _additionsHead: KeyValueChangeRecord_<K, V> = null;
private _additionsTail: KeyValueChangeRecord_<K, V> = null;
private _removalsHead: KeyValueChangeRecord_<K, V> = null;
private _removalsTail: KeyValueChangeRecord_<K, V> = null;
get isDirty(): boolean {
return this._additionsHead !== null || this._changesHead !== null ||
this._removalsHead !== null;
}
forEachItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
let record: KeyValueChangeRecord_<K, V>;
for (record = this._mapHead; record !== null; record = record._next) {
fn(record);
}
}
forEachPreviousItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
let record: KeyValueChangeRecord_<K, V>;
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
fn(record);
}
}
forEachChangedItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
let record: KeyValueChangeRecord_<K, V>;
for (record = this._changesHead; record !== null; record = record._nextChanged) {
fn(record);
}
}
forEachAddedItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
let record: KeyValueChangeRecord_<K, V>;
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record);
}
}
forEachRemovedItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
let record: KeyValueChangeRecord_<K, V>;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record);
}
}
diff(map: Map<any, any>|{[k: string]: any}): any {
if (!map) {
map = new Map();
} else if (!(map instanceof Map || isJsObject(map))) {
throw new Error(`Error trying to diff '${map}'`);
}
return this.check(map) ? this : null;
}
onDestroy() {}
check(map: Map<any, any>|{[k: string]: any}): boolean {
this._reset();
const records = this._records;
let oldSeqRecord: KeyValueChangeRecord_<K, V> = this._mapHead;
let lastOldSeqRecord: KeyValueChangeRecord_<K, V> = null;
let lastNewSeqRecord: KeyValueChangeRecord_<K, V> = null;
let seqChanged: boolean = false;
this._forEach(map, (value: any, key: any) => {
let newSeqRecord: any;
if (oldSeqRecord && key === oldSeqRecord.key) {
newSeqRecord = oldSeqRecord;
this._maybeAddToChanges(newSeqRecord, value);
} else {
seqChanged = true;
if (oldSeqRecord !== null) {
this._removeFromSeq(lastOldSeqRecord, oldSeqRecord);
this._addToRemovals(oldSeqRecord);
}
if (records.has(key)) {
newSeqRecord = records.get(key);
this._maybeAddToChanges(newSeqRecord, value);
} else {
newSeqRecord = new KeyValueChangeRecord_<K, V>(key);
records.set(key, newSeqRecord);
newSeqRecord.currentValue = value;
this._addToAdditions(newSeqRecord);
}
}
if (seqChanged) {
if (this._isInRemovals(newSeqRecord)) {
this._removeFromRemovals(newSeqRecord);
}
if (lastNewSeqRecord == null) {
this._mapHead = newSeqRecord;
} else {
lastNewSeqRecord._next = newSeqRecord;
}
}
lastOldSeqRecord = oldSeqRecord;
lastNewSeqRecord = newSeqRecord;
oldSeqRecord = oldSeqRecord && oldSeqRecord._next;
});
this._truncate(lastOldSeqRecord, oldSeqRecord);
return this.isDirty;
}
/** @internal */
_reset() {
if (this.isDirty) {
let record: KeyValueChangeRecord_<K, V>;
// Record the state of the mapping
for (record = this._previousMapHead = this._mapHead; record !== null; record = record._next) {
record._nextPrevious = record._next;
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
record.previousValue = record.currentValue;
}
for (record = this._additionsHead; record != null; record = record._nextAdded) {
record.previousValue = record.currentValue;
}
this._changesHead = this._changesTail = null;
this._additionsHead = this._additionsTail = null;
this._removalsHead = this._removalsTail = null;
}
}
private _truncate(lastRecord: KeyValueChangeRecord_<K, V>, record: KeyValueChangeRecord_<K, V>) {
while (record !== null) {
if (lastRecord === null) {
this._mapHead = null;
} else {
lastRecord._next = null;
}
const nextRecord = record._next;
this._addToRemovals(record);
lastRecord = record;
record = nextRecord;
}
for (let rec: KeyValueChangeRecord_<K, V> = this._removalsHead; rec !== null;
rec = rec._nextRemoved) {
rec.previousValue = rec.currentValue;
rec.currentValue = null;
this._records.delete(rec.key);
}
}
private _maybeAddToChanges(record: KeyValueChangeRecord_<K, V>, newValue: any): void {
if (!looseIdentical(newValue, record.currentValue)) {
record.previousValue = record.currentValue;
record.currentValue = newValue;
this._addToChanges(record);
}
}
private _isInRemovals(record: KeyValueChangeRecord_<K, V>) {
return record === this._removalsHead || record._nextRemoved !== null ||
record._prevRemoved !== null;
}
private _addToRemovals(record: KeyValueChangeRecord_<K, V>) {
if (this._removalsHead === null) {
this._removalsHead = this._removalsTail = record;
} else {
this._removalsTail._nextRemoved = record;
record._prevRemoved = this._removalsTail;
this._removalsTail = record;
}
}
private _removeFromSeq(prev: KeyValueChangeRecord_<K, V>, record: KeyValueChangeRecord_<K, V>) {
const next = record._next;
if (prev === null) {
this._mapHead = next;
} else {
prev._next = next;
}
record._next = null;
}
private _removeFromRemovals(record: KeyValueChangeRecord_<K, V>) {
const prev = record._prevRemoved;
const next = record._nextRemoved;
if (prev === null) {
this._removalsHead = next;
} else {
prev._nextRemoved = next;
}
if (next === null) {
this._removalsTail = prev;
} else {
next._prevRemoved = prev;
}
record._prevRemoved = record._nextRemoved = null;
}
private _addToAdditions(record: KeyValueChangeRecord_<K, V>) {
if (this._additionsHead === null) {
this._additionsHead = this._additionsTail = record;
} else {
this._additionsTail._nextAdded = record;
this._additionsTail = record;
}
}
private _addToChanges(record: KeyValueChangeRecord_<K, V>) {
if (this._changesHead === null) {
this._changesHead = this._changesTail = record;
} else {
this._changesTail._nextChanged = record;
this._changesTail = record;
}
}
toString(): string {
const items: any[] = [];
const previous: any[] = [];
const changes: any[] = [];
const additions: any[] = [];
const removals: any[] = [];
let record: KeyValueChangeRecord_<K, V>;
for (record = this._mapHead; record !== null; record = record._next) {
items.push(stringify(record));
}
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
previous.push(stringify(record));
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
changes.push(stringify(record));
}
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
additions.push(stringify(record));
}
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
removals.push(stringify(record));
}
return 'map: ' + items.join(', ') + '\n' +
'previous: ' + previous.join(', ') + '\n' +
'additions: ' + additions.join(', ') + '\n' +
'changes: ' + changes.join(', ') + '\n' +
'removals: ' + removals.join(', ') + '\n';
}
/** @internal */
private _forEach<K, V>(obj: Map<K, V>|{[k: string]: V}, fn: (v: V, k: any) => void) {
if (obj instanceof Map) {
obj.forEach(fn);
} else {
Object.keys(obj).forEach(k => fn(obj[k], k));
}
}
}
/**
* @stable
*/
class KeyValueChangeRecord_<K, V> implements KeyValueChangeRecord<K, V> {
previousValue: V = null;
currentValue: V = null;
/** @internal */
_nextPrevious: KeyValueChangeRecord_<K, V> = null;
/** @internal */
_next: KeyValueChangeRecord_<K, V> = null;
/** @internal */
_nextAdded: KeyValueChangeRecord_<K, V> = null;
/** @internal */
_nextRemoved: KeyValueChangeRecord_<K, V> = null;
/** @internal */
_prevRemoved: KeyValueChangeRecord_<K, V> = null;
/** @internal */
_nextChanged: KeyValueChangeRecord_<K, V> = null;
constructor(public key: K) {}
toString(): string {
return looseIdentical(this.previousValue, this.currentValue) ?
stringify(this.key) :
(stringify(this.key) + '[' + stringify(this.previousValue) + '->' +
stringify(this.currentValue) + ']');
}
}

View File

@ -0,0 +1,218 @@
/**
* @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 {Optional, Provider, SkipSelf} from '../../di';
import {ChangeDetectorRef} from '../change_detector_ref';
/**
* A type describing supported interable types.
*
* @stable
*/
export type NgIterable<T> = Array<T>| Iterable<T>;
/**
* A strategy for tracking changes over time to an iterable. Used by {@link NgFor} to
* respond to changes in an iterable by effecting equivalent changes in the DOM.
*
* @stable
*/
export interface IterableDiffer<V> {
/**
* Compute a difference between the previous state and the new `object` state.
*
* @param object containing the new value.
* @returns an object describing the difference. The return value is only valid until the next
* `diff()` invocation.
*/
diff(object: NgIterable<V>): IterableChanges<V>;
}
/**
* An object describing the changes in the `Iterable` collection since last time
* `IterableDiffer#diff()` was invoked.
*
* @stable
*/
export interface IterableChanges<V> {
/**
* Iterate over all changes. `IterableChangeRecord` will contain information about changes
* to each item.
*/
forEachItem(fn: (record: IterableChangeRecord<V>) => void): void;
/**
* Iterate over a set of operations which when applied to the original `Iterable` will produce the
* new `Iterable`.
*
* NOTE: These are not necessarily the actual operations which were applied to the original
* `Iterable`, rather these are a set of computed operations which may not be the same as the
* ones applied.
*
* @param record A change which needs to be applied
* @param previousIndex The `IterableChangeRecord#previousIndex` of the `record` refers to the
* original `Iterable` location, where as `previousIndex` refers to the transient location
* of the item, after applying the operations up to this point.
* @param currentIndex The `IterableChangeRecord#currentIndex` of the `record` refers to the
* original `Iterable` location, where as `currentIndex` refers to the transient location
* of the item, after applying the operations up to this point.
*/
forEachOperation(
fn: (record: IterableChangeRecord<V>, previousIndex: number, currentIndex: number) => void):
void;
/**
* Iterate over changes in the order of original `Iterable` showing where the original items
* have moved.
*/
forEachPreviousItem(fn: (record: IterableChangeRecord<V>) => void): void;
/** Iterate over all added items. */
forEachAddedItem(fn: (record: IterableChangeRecord<V>) => void): void;
/** Iterate over all moved items. */
forEachMovedItem(fn: (record: IterableChangeRecord<V>) => void): void;
/** Iterate over all removed items. */
forEachRemovedItem(fn: (record: IterableChangeRecord<V>) => void): void;
/** Iterate over all items which had their identity (as computed by the `trackByFn`) changed. */
forEachIdentityChange(fn: (record: IterableChangeRecord<V>) => void): void;
}
/**
* Record representing the item change information.
*
* @stable
*/
export interface IterableChangeRecord<V> {
/** Current index of the item in `Iterable` or null if removed. */
// TODO(TS2.1): make readonly once we move to TS v2.1
/* readonly */ currentIndex: number;
/** Previous index of the item in `Iterable` or null if added. */
// TODO(TS2.1): make readonly once we move to TS v2.1
/* readonly */ previousIndex: number;
/** The item. */
// TODO(TS2.1): make readonly once we move to TS v2.1
/* readonly */ item: V;
/** Track by identity as computed by the `trackByFn`. */
// TODO(TS2.1): make readonly once we move to TS v2.1
/* readonly */ trackById: any;
}
/**
* @deprecated v4.0.0 - Use IterableChangeRecord instead.
*/
export interface CollectionChangeRecord<V> extends IterableChangeRecord<V> {}
/**
* Nolonger used.
*
* @deprecated v4.0.0 - Use TrackByFunction instead
*/
export interface TrackByFn { (index: number, item: any): any; }
/**
* An optional function passed into {@link NgForOf} that defines how to track
* items in an iterable (e.g. fby index or id)
*
* @stable
*/
export interface TrackByFunction<T> { (index: number, item: T): any; }
/**
* Provides a factory for {@link IterableDiffer}.
*
* @stable
*/
export interface IterableDifferFactory {
supports(objects: any): boolean;
create<V>(trackByFn?: TrackByFunction<V>): IterableDiffer<V>;
/**
* @deprecated v4.0.0 - ChangeDetectorRef is not used and is no longer a parameter
*/
create<V>(_cdr?: ChangeDetectorRef|TrackByFunction<V>, trackByFn?: TrackByFunction<V>):
IterableDiffer<V>;
}
/**
* A repository of different iterable diffing strategies used by NgFor, NgClass, and others.
* @stable
*/
export class IterableDiffers {
/**
* @deprecated v4.0.0 - Should be private
*/
factories: IterableDifferFactory[];
constructor(factories: IterableDifferFactory[]) { this.factories = factories; }
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
if (parent != null) {
const copied = parent.factories.slice();
factories = factories.concat(copied);
return new IterableDiffers(factories);
} else {
return new IterableDiffers(factories);
}
}
/**
* Takes an array of {@link IterableDifferFactory} and returns a provider used to extend the
* inherited {@link IterableDiffers} instance with the provided factories and return a new
* {@link IterableDiffers} instance.
*
* The following example shows how to extend an existing list of factories,
* which will only be applied to the injector for this component and its children.
* This step is all that's required to make a new {@link IterableDiffer} available.
*
* ### Example
*
* ```
* @Component({
* viewProviders: [
* IterableDiffers.extend([new ImmutableListDiffer()])
* ]
* })
* ```
*/
static extend(factories: IterableDifferFactory[]): Provider {
return {
provide: IterableDiffers,
useFactory: (parent: IterableDiffers) => {
if (!parent) {
// Typically would occur when calling IterableDiffers.extend inside of dependencies passed
// to
// bootstrap(), which would override default pipes instead of extending them.
throw new Error('Cannot extend IterableDiffers without a parent injector');
}
return IterableDiffers.create(factories, parent);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[IterableDiffers, new SkipSelf(), new Optional()]]
};
}
find(iterable: any): IterableDifferFactory {
const factory = this.factories.find(f => f.supports(iterable));
if (factory != null) {
return factory;
} else {
throw new Error(
`Cannot find a differ supporting object '${iterable}' of type '${getTypeNameForDebugging(iterable)}'`);
}
}
}
export function getTypeNameForDebugging(type: any): string {
return type['name'] || typeof type;
}

View File

@ -0,0 +1,182 @@
/**
* @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 {Optional, Provider, SkipSelf} from '../../di';
import {ChangeDetectorRef} from '../change_detector_ref';
/**
* A differ that tracks changes made to an object over time.
*
* @stable
*/
export interface KeyValueDiffer<K, V> {
/**
* Compute a difference between the previous state and the new `object` state.
*
* @param object containing the new value.
* @returns an object describing the difference. The return value is only valid until the next
* `diff()` invocation.
*/
diff(object: Map<K, V>): KeyValueChanges<K, V>;
/**
* Compute a difference between the previous state and the new `object` state.
*
* @param object containing the new value.
* @returns an object describing the difference. The return value is only valid until the next
* `diff()` invocation.
*/
diff(object: {[key: string]: V}): KeyValueChanges<string, V>;
// TODO(TS2.1): diff<KP extends string>(this: KeyValueDiffer<KP, V>, object: Record<KP, V>):
// KeyValueDiffer<KP, V>;
}
/**
* An object describing the changes in the `Map` or `{[k:string]: string}` since last time
* `KeyValueDiffer#diff()` was invoked.
*
* @stable
*/
export interface KeyValueChanges<K, V> {
/**
* Iterate over all changes. `KeyValueChangeRecord` will contain information about changes
* to each item.
*/
forEachItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
/**
* Iterate over changes in the order of original Map showing where the original items
* have moved.
*/
forEachPreviousItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
/**
* Iterate over all keys for which values have changed.
*/
forEachChangedItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
/**
* Iterate over all added items.
*/
forEachAddedItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
/**
* Iterate over all removed items.
*/
forEachRemovedItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
}
/**
* Record representing the item change information.
*
* @stable
*/
export interface KeyValueChangeRecord<K, V> {
/**
* Current key in the Map.
*/
/* readonly */ key: K;
/**
* Current value for the key or `undefined` if removed.
*/
/* readonly */ currentValue: V;
/**
* Previous value for the key or `undefined` if added.
*/
/* readonly */ previousValue: V;
}
/**
* Provides a factory for {@link KeyValueDiffer}.
*
* @stable
*/
export interface KeyValueDifferFactory {
/**
* Test to see if the differ knows how to diff this kind of object.
*/
supports(objects: any): boolean;
/**
* Create a `KeyValueDiffer`.
*/
create<K, V>(): KeyValueDiffer<K, V>;
/**
* @deprecated v4.0.0 - ChangeDetectorRef is not used and is no longer a parameter
*/
create<K, V>(_cdr?: ChangeDetectorRef): KeyValueDiffer<K, V>;
}
/**
* A repository of different Map diffing strategies used by NgClass, NgStyle, and others.
* @stable
*/
export class KeyValueDiffers {
/**
* @deprecated v4.0.0 - Should be private.
*/
factories: KeyValueDifferFactory[];
constructor(factories: KeyValueDifferFactory[]) { this.factories = factories; }
static create<S>(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
if (parent) {
const copied = parent.factories.slice();
factories = factories.concat(copied);
}
return new KeyValueDiffers(factories);
}
/**
* Takes an array of {@link KeyValueDifferFactory} and returns a provider used to extend the
* inherited {@link KeyValueDiffers} instance with the provided factories and return a new
* {@link KeyValueDiffers} instance.
*
* The following example shows how to extend an existing list of factories,
* which will only be applied to the injector for this component and its children.
* This step is all that's required to make a new {@link KeyValueDiffer} available.
*
* ### Example
*
* ```
* @Component({
* viewProviders: [
* KeyValueDiffers.extend([new ImmutableMapDiffer()])
* ]
* })
* ```
*/
static extend<S>(factories: KeyValueDifferFactory[]): Provider {
return {
provide: KeyValueDiffers,
useFactory: (parent: KeyValueDiffers) => {
if (!parent) {
// Typically would occur when calling KeyValueDiffers.extend inside of dependencies passed
// to bootstrap(), which would override default pipes instead of extending them.
throw new Error('Cannot extend KeyValueDiffers without a parent injector');
}
return KeyValueDiffers.create(factories, parent);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[KeyValueDiffers, new SkipSelf(), new Optional()]]
};
}
find(kv: any): KeyValueDifferFactory {
const factory = this.factories.find(f => f.supports(kv));
if (factory) {
return factory;
}
throw new Error(`Cannot find a differ supporting object '${kv}'`);
}
}

View File

@ -0,0 +1,38 @@
/**
* @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
*/
/**
* To create a Pipe, you must implement this interface.
*
* Angular invokes the `transform` method with the value of a binding
* as the first argument, and any parameters as the second argument in list form.
*
* ## Syntax
*
* `value | pipeName[:arg0[:arg1...]]`
*
* ### Example ([live demo](http://plnkr.co/edit/f5oyIked9M2cKzvZNKHV?p=preview))
*
* The `RepeatPipe` below repeats the value as many times as indicated by the first argument:
*
* ```
* import {Pipe, PipeTransform} from '@angular/core';
*
* @Pipe({name: 'repeat'})
* export class RepeatPipe implements PipeTransform {
* transform(value: any, times: number) {
* return value.repeat(times);
* }
* }
* ```
*
* Invoking `{{ 'ok' | repeat:3 }}` in a template produces `okokok`.
*
* @stable
*/
export interface PipeTransform { transform(value: any, ...args: any[]): any; }

View File

@ -0,0 +1,13 @@
/**
* @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
*/
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
export {NgModuleInjector as ɵNgModuleInjector} from './linker/ng_module_factory';
export {registerModuleFactory as ɵregisterModuleFactory} from './linker/ng_module_factory_loader';
export {reflector as ɵreflector} from './reflection/reflection';
export {ArgumentType as ɵArgumentType, BindingType as ɵBindingType, DepFlags as ɵDepFlags, EMPTY_ARRAY as ɵEMPTY_ARRAY, EMPTY_MAP as ɵEMPTY_MAP, NodeFlags as ɵNodeFlags, QueryBindingType as ɵQueryBindingType, QueryValueType as ɵQueryValueType, ViewDefinition as ɵViewDefinition, ViewFlags as ɵViewFlags, anchorDef as ɵand, createComponentFactory as ɵccf, createRendererType2 as ɵcrt, directiveDef as ɵdid, elementDef as ɵeld, elementEventFullName as ɵelementEventFullName, getComponentViewDefinitionFactory as ɵgetComponentViewDefinitionFactory, inlineInterpolate as ɵinlineInterpolate, interpolate as ɵinterpolate, ngContentDef as ɵncd, nodeValue as ɵnov, pipeDef as ɵpid, providerDef as ɵprd, pureArrayDef as ɵpad, pureObjectDef as ɵpod, purePipeDef as ɵppd, queryDef as ɵqud, textDef as ɵted, unwrapValue as ɵunv, viewDef as ɵvid} from './view/index';

View File

@ -0,0 +1,22 @@
/**
* @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 {Injectable} from './di';
@Injectable()
export class Console {
log(message: string): void {
// tslint:disable-next-line:no-console
console.log(message);
}
// Note: for reporting errors use `DOM.logError()` as it is platform specific
warn(message: string): void {
// tslint:disable-next-line:no-console
console.warn(message);
}
}

61
packages/core/src/core.ts Normal file
View File

@ -0,0 +1,61 @@
/**
* @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
*/
/**
* @module
* @description
* Entry point from which you should import all public core APIs.
*/
export * from './metadata';
export * from './version';
export {Class, ClassDefinition, TypeDecorator} from './util/decorators';
export * from './di';
export {createPlatform, assertPlatform, destroyPlatform, getPlatform, PlatformRef, ApplicationRef, enableProdMode, isDevMode, createPlatformFactory, NgProbeToken} from './application_ref';
export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, PLATFORM_ID, APP_BOOTSTRAP_LISTENER} from './application_tokens';
export {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
export * from './zone';
export * from './render';
export * from './linker';
export {DebugElement, DebugNode, asNativeElements, getDebugNode, Predicate} from './debug/debug_node';
export {GetTestability, Testability, TestabilityRegistry, setTestabilityGetter} from './testability/testability';
export * from './change_detection';
export * from './platform_core_providers';
export {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID, MissingTranslationStrategy} from './i18n/tokens';
export {ApplicationModule} from './application_module';
export {wtfCreateScope, wtfLeave, wtfStartTimeRange, wtfEndTimeRange, WtfScopeFn} from './profile/profile';
export {Type} from './type';
export {EventEmitter} from './event_emitter';
export {ErrorHandler} from './error_handler';
export * from './core_private_export';
export {Sanitizer, SecurityContext} from './security';
export * from './codegen_private_exports';
export * from './animation/animation_metadata_wrapped';
import {AnimationTriggerMetadata} from './animation/animation_metadata_wrapped';
// For backwards compatibility.
/**
* @deprecated from v4
*/
export type AnimationEntryMetadata = any;
/**
* @deprecated from v4
*/
export type AnimationStateTransitionMetadata = any;
/**
* @deprecated from v4
*/
export type AnimationPlayer = any;
/**
* @deprecated from v4
*/
export type AnimationStyles = any;
/**
* @deprecated from v4
*/
export type AnimationKeyframe = any;

View File

@ -0,0 +1,28 @@
/**
* @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
*/
export {ALLOW_MULTIPLE_PLATFORMS as ɵALLOW_MULTIPLE_PLATFORMS} from './application_ref';
export {APP_ID_RANDOM_PROVIDER as ɵAPP_ID_RANDOM_PROVIDER} from './application_tokens';
export {ValueUnwrapper as ɵValueUnwrapper, devModeEqual as ɵdevModeEqual} from './change_detection/change_detection_util';
export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/change_detection_util';
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
export {Console as ɵConsole} from './console';
export {ERROR_COMPONENT_TYPE as ɵERROR_COMPONENT_TYPE} from './errors';
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
export {LIFECYCLE_HOOKS_VALUES as ɵLIFECYCLE_HOOKS_VALUES, LifecycleHooks as ɵLifecycleHooks} from './metadata/lifecycle_hooks';
export {ViewMetadata as ɵViewMetadata} from './metadata/view';
export {Reflector as ɵReflector, reflector as ɵreflector} from './reflection/reflection';
// We need to import this name separately from the above wildcard, because this symbol is exposed.
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';
export {ReflectorReader as ɵReflectorReader} from './reflection/reflector_reader';
export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types';
export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo} from './render/api';
export {global as ɵglobal, looseIdentical as ɵlooseIdentical, stringify as ɵstringify} from './util';
export {makeDecorator as ɵmakeDecorator} from './util/decorators';
export {isObservable as ɵisObservable, isPromise as ɵisPromise, merge as ɵmerge} from './util/lang';

View File

@ -0,0 +1,201 @@
/**
* @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 {Injector} from '../di';
import {RenderDebugInfo} from '../render/api';
export class EventListener { constructor(public name: string, public callback: Function){}; }
/**
* @experimental All debugging apis are currently experimental.
*/
export class DebugNode {
nativeNode: any;
listeners: EventListener[];
parent: DebugElement;
constructor(nativeNode: any, parent: DebugNode, private _debugInfo: RenderDebugInfo) {
this.nativeNode = nativeNode;
if (parent && parent instanceof DebugElement) {
parent.addChild(this);
} else {
this.parent = null;
}
this.listeners = [];
}
get injector(): Injector { return this._debugInfo ? this._debugInfo.injector : null; }
get componentInstance(): any { return this._debugInfo ? this._debugInfo.component : null; }
get context(): any { return this._debugInfo ? this._debugInfo.context : null; }
get references(): {[key: string]: any} {
return this._debugInfo ? this._debugInfo.references : null;
}
get providerTokens(): any[] { return this._debugInfo ? this._debugInfo.providerTokens : null; }
get source(): string { return this._debugInfo ? this._debugInfo.source : null; }
}
/**
* @experimental All debugging apis are currently experimental.
*/
export class DebugElement extends DebugNode {
name: string;
properties: {[key: string]: any};
attributes: {[key: string]: string};
classes: {[key: string]: boolean};
styles: {[key: string]: string};
childNodes: DebugNode[];
nativeElement: any;
constructor(nativeNode: any, parent: any, _debugInfo: RenderDebugInfo) {
super(nativeNode, parent, _debugInfo);
this.properties = {};
this.attributes = {};
this.classes = {};
this.styles = {};
this.childNodes = [];
this.nativeElement = nativeNode;
}
addChild(child: DebugNode) {
if (child) {
this.childNodes.push(child);
child.parent = this;
}
}
removeChild(child: DebugNode) {
const childIndex = this.childNodes.indexOf(child);
if (childIndex !== -1) {
child.parent = null;
this.childNodes.splice(childIndex, 1);
}
}
insertChildrenAfter(child: DebugNode, newChildren: DebugNode[]) {
const siblingIndex = this.childNodes.indexOf(child);
if (siblingIndex !== -1) {
this.childNodes.splice(siblingIndex + 1, 0, ...newChildren);
newChildren.forEach(c => {
if (c.parent) {
c.parent.removeChild(c);
}
c.parent = this;
});
}
}
insertBefore(refChild: DebugNode, newChild: DebugNode): void {
const refIndex = this.childNodes.indexOf(refChild);
if (refIndex === -1) {
this.addChild(newChild);
} else {
if (newChild.parent) {
newChild.parent.removeChild(newChild);
}
newChild.parent = this;
this.childNodes.splice(refIndex, 0, newChild);
}
}
query(predicate: Predicate<DebugElement>): DebugElement {
const results = this.queryAll(predicate);
return results[0] || null;
}
queryAll(predicate: Predicate<DebugElement>): DebugElement[] {
const matches: DebugElement[] = [];
_queryElementChildren(this, predicate, matches);
return matches;
}
queryAllNodes(predicate: Predicate<DebugNode>): DebugNode[] {
const matches: DebugNode[] = [];
_queryNodeChildren(this, predicate, matches);
return matches;
}
get children(): DebugElement[] {
return this.childNodes.filter((node) => node instanceof DebugElement) as DebugElement[];
}
triggerEventHandler(eventName: string, eventObj: any) {
this.listeners.forEach((listener) => {
if (listener.name == eventName) {
listener.callback(eventObj);
}
});
}
}
/**
* @experimental
*/
export function asNativeElements(debugEls: DebugElement[]): any {
return debugEls.map((el) => el.nativeElement);
}
function _queryElementChildren(
element: DebugElement, predicate: Predicate<DebugElement>, matches: DebugElement[]) {
element.childNodes.forEach(node => {
if (node instanceof DebugElement) {
if (predicate(node)) {
matches.push(node);
}
_queryElementChildren(node, predicate, matches);
}
});
}
function _queryNodeChildren(
parentNode: DebugNode, predicate: Predicate<DebugNode>, matches: DebugNode[]) {
if (parentNode instanceof DebugElement) {
parentNode.childNodes.forEach(node => {
if (predicate(node)) {
matches.push(node);
}
if (node instanceof DebugElement) {
_queryNodeChildren(node, predicate, matches);
}
});
}
}
// Need to keep the nodes in a global Map so that multiple angular apps are supported.
const _nativeNodeToDebugNode = new Map<any, DebugNode>();
/**
* @experimental
*/
export function getDebugNode(nativeNode: any): DebugNode {
return _nativeNodeToDebugNode.get(nativeNode);
}
export function getAllDebugNodes(): DebugNode[] {
return Array.from(_nativeNodeToDebugNode.values());
}
export function indexDebugNode(node: DebugNode) {
_nativeNodeToDebugNode.set(node.nativeNode, node);
}
export function removeDebugNodeFromIndex(node: DebugNode) {
_nativeNodeToDebugNode.delete(node.nativeNode);
}
/**
* A boolean-valued function over a value, possibly including context information
* regarding that value's position in an array.
*
* @experimental All debugging apis are currently experimental.
*/
export interface Predicate<T> { (value: T, index?: number, array?: T[]): boolean; }

24
packages/core/src/di.ts Normal file
View File

@ -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
*/
/**
* @module
* @description
* The `di` module provides dependency injection container services.
*/
export * from './di/metadata';
export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
export {Injector} from './di/injector';
export {ReflectiveInjector} from './di/reflective_injector';
export {Provider, TypeProvider, ValueProvider, ClassProvider, ExistingProvider, FactoryProvider} from './di/provider';
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken, OpaqueToken} from './di/injection_token';

View File

@ -0,0 +1,61 @@
/**
* @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 {Type} from '../type';
import {stringify} from '../util';
/**
* An interface that a function passed into {@link forwardRef} has to implement.
*
* ### Example
*
* {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref_fn'}
* @experimental
*/
export interface ForwardRefFn { (): any; }
/**
* Allows to refer to references which are not yet defined.
*
* For instance, `forwardRef` is used when the `token` which we need to refer to for the purposes of
* DI is declared,
* but not yet defined. It is also used when the `token` which we use when creating a query is not
* yet defined.
*
* ### Example
* {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref'}
* @experimental
*/
export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
(<any>forwardRefFn).__forward_ref__ = forwardRef;
(<any>forwardRefFn).toString = function() { return stringify(this()); };
return (<Type<any>><any>forwardRefFn);
}
/**
* Lazily retrieves the reference value from a forwardRef.
*
* Acts as the identity function when given a non-forward-ref value.
*
* ### Example ([live demo](http://plnkr.co/edit/GU72mJrk1fiodChcmiDR?p=preview))
*
* {@example core/di/ts/forward_ref/forward_ref_spec.ts region='resolve_forward_ref'}
*
* See: {@link forwardRef}
* @experimental
*/
export function resolveForwardRef(type: any): any {
if (typeof type === 'function' && type.hasOwnProperty('__forward_ref__') &&
type.__forward_ref__ === forwardRef) {
return (<ForwardRefFn>type)();
} else {
return type;
}
}

View File

@ -0,0 +1,67 @@
/**
* @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
*/
/**
* Creates a token that can be used in a DI Provider.
*
* ### Example ([live demo](http://plnkr.co/edit/Ys9ezXpj2Mnoy3Uc8KBp?p=preview))
*
* ```typescript
* var t = new OpaqueToken("value");
*
* var injector = Injector.resolveAndCreate([
* {provide: t, useValue: "bindingValue"}
* ]);
*
* expect(injector.get(t)).toEqual("bindingValue");
* ```
*
* Using an `OpaqueToken` is preferable to using strings as tokens because of possible collisions
* caused by multiple providers using the same string as two different tokens.
*
* Using an `OpaqueToken` is preferable to using an `Object` as tokens because it provides better
* error messages.
* @deprecated since v4.0.0 because it does not support type information, use `InjectionToken<?>`
* instead.
*/
export class OpaqueToken {
constructor(protected _desc: string) {}
toString(): string { return `Token ${this._desc}`; }
}
/**
* Creates a token that can be used in a DI Provider.
*
* Use an `InjectionToken` whenever the type you are injecting is not reified (does not have a
* runtime representation) such as when injecting an interface, callable type, array or
* parametrized type.
*
* `InjectionToken` is parametrize on `T` which is the type of object which will be returned by the
* `Injector`. This provides additional level of type safety.
*
* ```
* interface MyInterface {...}
* var myInterface = injector.get(new InjectionToken<MyInterface>('SomeToken'));
* // myInterface is inferred to be MyInterface.
* ```
*
* ### Example
*
* {@example core/di/ts/injector_spec.ts region='Injector'}
*
* @stable
*/
export class InjectionToken<T> extends OpaqueToken {
// This unused property is needed here so that TS can differentiate InjectionToken from
// OpaqueToken since otherwise they would have the same shape and be treated as equivalent.
private _differentiate_from_OpaqueToken_structurally: any;
constructor(desc: string) { super(desc); }
toString(): string { return `InjectionToken ${this._desc}`; }
}

View File

@ -0,0 +1,63 @@
/**
* @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 {Type} from '../type';
import {stringify} from '../util';
import {InjectionToken} from './injection_token';
const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
class _NullInjector implements Injector {
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
if (notFoundValue === _THROW_IF_NOT_FOUND) {
throw new Error(`No provider for ${stringify(token)}!`);
}
return notFoundValue;
}
}
/**
* @whatItDoes Injector interface
* @howToUse
* ```
* const injector: Injector = ...;
* injector.get(...);
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/injector_spec.ts region='Injector'}
*
* `Injector` returns itself when given `Injector` as a token:
* {@example core/di/ts/injector_spec.ts region='injectInjector'}
*
* @stable
*/
export abstract class Injector {
static THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
static NULL: Injector = new _NullInjector();
/**
* Retrieves an instance from the injector based on the provided token.
* If not found:
* - Throws {@link NoProviderError} if no `notFoundValue` that is not equal to
* Injector.THROW_IF_NOT_FOUND is given
* - Returns the `notFoundValue` otherwise
*/
abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;
/**
* @deprecated from v4.0.0 use Type<T> or InjectToken<T>
* @suppress {duplicate}
*/
abstract get(token: any, notFoundValue?: any): any;
}

View File

@ -0,0 +1,288 @@
/**
* @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 {makeDecorator, makeParamDecorator} from '../util/decorators';
/**
* Type of the Inject decorator / constructor function.
*
* @stable
*/
export interface InjectDecorator {
/**
* @whatItDoes A parameter decorator that specifies a dependency.
* @howToUse
* ```
* @Injectable()
* class Car {
* constructor(@Inject("MyEngine") public engine:Engine) {}
* }
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/metadata_spec.ts region='Inject'}
*
* When `@Inject()` is not present, {@link Injector} will use the type annotation of the
* parameter.
*
* ### Example
*
* {@example core/di/ts/metadata_spec.ts region='InjectWithoutDecorator'}
*
* @stable
*/
(token: any): any;
new (token: any): Inject;
}
/**
* Type of the Inject metadata.
*
* @stable
*/
export interface Inject { token: any; }
/**
* Inject decorator and metadata.
*
* @stable
* @Annotation
*/
export const Inject: InjectDecorator = makeParamDecorator('Inject', [['token', undefined]]);
/**
* Type of the Optional decorator / constructor function.
*
* @stable
*/
export interface OptionalDecorator {
/**
* @whatItDoes A parameter metadata that marks a dependency as optional.
* {@link Injector} provides `null` if the dependency is not found.
* @howToUse
* ```
* @Injectable()
* class Car {
* constructor(@Optional() public engine:Engine) {}
* }
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/metadata_spec.ts region='Optional'}
*
* @stable
*/
(): any;
new (): Optional;
}
/**
* Type of the Optional metadata.
*
* @stable
*/
export interface Optional {}
/**
* Optional decorator and metadata.
*
* @stable
* @Annotation
*/
export const Optional: OptionalDecorator = makeParamDecorator('Optional', []);
/**
* Type of the Injectable decorator / constructor function.
*
* @stable
*/
export interface InjectableDecorator {
/**
* @whatItDoes A marker metadata that marks a class as available to {@link Injector} for creation.
* @howToUse
* ```
* @Injectable()
* class Car {}
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/metadata_spec.ts region='Injectable'}
*
* {@link Injector} will throw {@link NoAnnotationError} when trying to instantiate a class that
* does not have `@Injectable` marker, as shown in the example below.
*
* {@example core/di/ts/metadata_spec.ts region='InjectableThrows'}
*
* @stable
*/
(): any;
new (): Injectable;
}
/**
* Type of the Injectable metadata.
*
* @stable
*/
export interface Injectable {}
/**
* Injectable decorator and metadata.
*
* @stable
* @Annotation
*/
export const Injectable: InjectableDecorator = <InjectableDecorator>makeDecorator('Injectable', []);
/**
* Type of the Self decorator / constructor function.
*
* @stable
*/
export interface SelfDecorator {
/**
* @whatItDoes Specifies that an {@link Injector} should retrieve a dependency only from itself.
* @howToUse
* ```
* @Injectable()
* class Car {
* constructor(@Self() public engine:Engine) {}
* }
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/metadata_spec.ts region='Self'}
*
* @stable
*/
(): any;
new (): Self;
}
/**
* Type of the Self metadata.
*
* @stable
*/
export interface Self {}
/**
* Self decorator and metadata.
*
* @stable
* @Annotation
*/
export const Self: SelfDecorator = makeParamDecorator('Self', []);
/**
* Type of the SkipSelf decorator / constructor function.
*
* @stable
*/
export interface SkipSelfDecorator {
/**
* @whatItDoes Specifies that the dependency resolution should start from the parent injector.
* @howToUse
* ```
* @Injectable()
* class Car {
* constructor(@SkipSelf() public engine:Engine) {}
* }
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/metadata_spec.ts region='SkipSelf'}
*
* @stable
*/
(): any;
new (): SkipSelf;
}
/**
* Type of the SkipSelf metadata.
*
* @stable
*/
export interface SkipSelf {}
/**
* SkipSelf decorator and metadata.
*
* @stable
* @Annotation
*/
export const SkipSelf: SkipSelfDecorator = makeParamDecorator('SkipSelf', []);
/**
* Type of the Host decorator / constructor function.
*
* @stable
*/
export interface HostDecorator {
/**
* @whatItDoes Specifies that an injector should retrieve a dependency from any injector until
* reaching the host element of the current component.
* @howToUse
* ```
* @Injectable()
* class Car {
* constructor(@Host() public engine:Engine) {}
* }
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/metadata_spec.ts region='Host'}
*
* @stable
*/
(): any;
new (): Host;
}
/**
* Type of the Host metadata.
*
* @stable
*/
export interface Host {}
/**
* Host decorator and metadata.
*
* @stable
* @Annotation
*/
export const Host: HostDecorator = makeParamDecorator('Host', []);

View File

@ -0,0 +1,220 @@
/**
* @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 {Type} from '../type';
/**
* @whatItDoes Configures the {@link Injector} to return an instance of `Type` when `Type' is used
* as token.
* @howToUse
* ```
* @Injectable()
* class MyService {}
*
* const provider: TypeProvider = MyService;
* ```
*
* @description
*
* Create an instance by invoking the `new` operator and supplying additional arguments.
* This form is a short form of `TypeProvider`;
*
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='TypeProvider'}
*
* @stable
*/
export interface TypeProvider extends Type<any> {}
/**
* @whatItDoes Configures the {@link Injector} to return a value for a token.
* @howToUse
* ```
* const provider: ValueProvider = {provide: 'someToken', useValue: 'someValue'};
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='ValueProvider'}
*
* @stable
*/
export interface ValueProvider {
/**
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
/**
* The value to inject.
*/
useValue: any;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
*/
multi?: boolean;
}
/**
* @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token.
* @howToUse
* ```
* @Injectable()
* class MyService {}
*
* const provider: ClassProvider = {provide: 'someToken', useClass: MyService};
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='ClassProvider'}
*
* Note that following two providers are not equal:
* {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'}
*
* @stable
*/
export interface ClassProvider {
/**
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
/**
* Class to instantiate for the `token`.
*/
useClass: Type<any>;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
*/
multi?: boolean;
}
/**
* @whatItDoes Configures the {@link Injector} to return a value of another `useExisting` token.
* @howToUse
* ```
* const provider: ExistingProvider = {provide: 'someToken', useExisting: 'someOtherToken'};
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='ExistingProvider'}
*
* @stable
*/
export interface ExistingProvider {
/**
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
/**
* Existing `token` to return. (equivalent to `injector.get(useExisting)`)
*/
useExisting: any;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
*/
multi?: boolean;
}
/**
* @whatItDoes Configures the {@link Injector} to return a value by invoking a `useFactory`
* function.
* @howToUse
* ```
* function serviceFactory() { ... }
*
* const provider: FactoryProvider = {provide: 'someToken', useFactory: serviceFactory, deps: []};
* ```
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='FactoryProvider'}
*
* Dependencies can also be marked as optional:
* {@example core/di/ts/provider_spec.ts region='FactoryProviderOptionalDeps'}
*
* @stable
*/
export interface FactoryProvider {
/**
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
/**
* A function to invoke to create a value for this `token`. The function is invoked with
* resolved values of `token`s in the `deps` field.
*/
useFactory: Function;
/**
* A list of `token`s which need to be resolved by the injector. The list of values is then
* used as arguments to the `useFactory` function.
*/
deps?: any[];
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*
* ### Example
*
* {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
*/
multi?: boolean;
}
/**
* @whatItDoes Describes how the {@link Injector} should be configured.
* @howToUse
* See {@link TypeProvider}, {@link ValueProvider}, {@link ClassProvider}, {@link ExistingProvider},
* {@link FactoryProvider}.
*
* @description
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
*
* @stable
*/
export type Provider =
TypeProvider | ValueProvider | ClassProvider | ExistingProvider | FactoryProvider | any[];

View File

@ -0,0 +1,240 @@
/**
* @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 {wrappedError} from '../error_handler';
import {ERROR_ORIGINAL_ERROR, getOriginalError} from '../errors';
import {Type} from '../type';
import {stringify} from '../util';
import {ReflectiveInjector} from './reflective_injector';
import {ReflectiveKey} from './reflective_key';
function findFirstClosedCycle(keys: any[]): any[] {
const res: any[] = [];
for (let i = 0; i < keys.length; ++i) {
if (res.indexOf(keys[i]) > -1) {
res.push(keys[i]);
return res;
}
res.push(keys[i]);
}
return res;
}
function constructResolvingPath(keys: any[]): string {
if (keys.length > 1) {
const reversed = findFirstClosedCycle(keys.slice().reverse());
const tokenStrs = reversed.map(k => stringify(k.token));
return ' (' + tokenStrs.join(' -> ') + ')';
}
return '';
}
export interface InjectionError extends Error {
keys: ReflectiveKey[];
injectors: ReflectiveInjector[];
constructResolvingMessage: (this: InjectionError) => string;
addKey(injector: ReflectiveInjector, key: ReflectiveKey): void;
}
function injectionError(
injector: ReflectiveInjector, key: ReflectiveKey,
constructResolvingMessage: (this: InjectionError) => string,
originalError?: Error): InjectionError {
const error = (originalError ? wrappedError('', originalError) : Error()) as InjectionError;
error.addKey = addKey;
error.keys = [key];
error.injectors = [injector];
error.constructResolvingMessage = constructResolvingMessage;
error.message = error.constructResolvingMessage();
(error as any)[ERROR_ORIGINAL_ERROR] = originalError;
return error;
}
function addKey(this: InjectionError, injector: ReflectiveInjector, key: ReflectiveKey): void {
this.injectors.push(injector);
this.keys.push(key);
this.message = this.constructResolvingMessage();
}
/**
* Thrown when trying to retrieve a dependency by key from {@link Injector}, but the
* {@link Injector} does not have a {@link Provider} for the given key.
*
* ### Example ([live demo](http://plnkr.co/edit/vq8D3FRB9aGbnWJqtEPE?p=preview))
*
* ```typescript
* class A {
* constructor(b:B) {}
* }
*
* expect(() => Injector.resolveAndCreate([A])).toThrowError();
* ```
*/
export function noProviderError(injector: ReflectiveInjector, key: ReflectiveKey): InjectionError {
return injectionError(injector, key, function(this: InjectionError) {
const first = stringify(this.keys[0].token);
return `No provider for ${first}!${constructResolvingPath(this.keys)}`;
});
}
/**
* Thrown when dependencies form a cycle.
*
* ### Example ([live demo](http://plnkr.co/edit/wYQdNos0Tzql3ei1EV9j?p=info))
*
* ```typescript
* var injector = Injector.resolveAndCreate([
* {provide: "one", useFactory: (two) => "two", deps: [[new Inject("two")]]},
* {provide: "two", useFactory: (one) => "one", deps: [[new Inject("one")]]}
* ]);
*
* expect(() => injector.get("one")).toThrowError();
* ```
*
* Retrieving `A` or `B` throws a `CyclicDependencyError` as the graph above cannot be constructed.
*/
export function cyclicDependencyError(
injector: ReflectiveInjector, key: ReflectiveKey): InjectionError {
return injectionError(injector, key, function(this: InjectionError) {
return `Cannot instantiate cyclic dependency!${constructResolvingPath(this.keys)}`;
});
}
/**
* Thrown when a constructing type returns with an Error.
*
* The `InstantiationError` class contains the original error plus the dependency graph which caused
* this object to be instantiated.
*
* ### Example ([live demo](http://plnkr.co/edit/7aWYdcqTQsP0eNqEdUAf?p=preview))
*
* ```typescript
* class A {
* constructor() {
* throw new Error('message');
* }
* }
*
* var injector = Injector.resolveAndCreate([A]);
* try {
* injector.get(A);
* } catch (e) {
* expect(e instanceof InstantiationError).toBe(true);
* expect(e.originalException.message).toEqual("message");
* expect(e.originalStack).toBeDefined();
* }
* ```
*/
export function instantiationError(
injector: ReflectiveInjector, originalException: any, originalStack: any,
key: ReflectiveKey): InjectionError {
return injectionError(injector, key, function(this: InjectionError) {
const first = stringify(this.keys[0].token);
return `${getOriginalError(this).message}: Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`;
}, originalException);
}
/**
* Thrown when an object other then {@link Provider} (or `Type`) is passed to {@link Injector}
* creation.
*
* ### Example ([live demo](http://plnkr.co/edit/YatCFbPAMCL0JSSQ4mvH?p=preview))
*
* ```typescript
* expect(() => Injector.resolveAndCreate(["not a type"])).toThrowError();
* ```
*/
export function invalidProviderError(provider: any) {
return Error(
`Invalid provider - only instances of Provider and Type are allowed, got: ${provider}`);
}
/**
* Thrown when the class has no annotation information.
*
* Lack of annotation information prevents the {@link Injector} from determining which dependencies
* need to be injected into the constructor.
*
* ### Example ([live demo](http://plnkr.co/edit/rHnZtlNS7vJOPQ6pcVkm?p=preview))
*
* ```typescript
* class A {
* constructor(b) {}
* }
*
* expect(() => Injector.resolveAndCreate([A])).toThrowError();
* ```
*
* This error is also thrown when the class not marked with {@link Injectable} has parameter types.
*
* ```typescript
* class B {}
*
* class A {
* constructor(b:B) {} // no information about the parameter types of A is available at runtime.
* }
*
* expect(() => Injector.resolveAndCreate([A,B])).toThrowError();
* ```
* @stable
*/
export function noAnnotationError(typeOrFunc: Type<any>| Function, params: any[][]): Error {
const signature: string[] = [];
for (let i = 0, ii = params.length; i < ii; i++) {
const parameter = params[i];
if (!parameter || parameter.length == 0) {
signature.push('?');
} else {
signature.push(parameter.map(stringify).join(' '));
}
}
return Error(
'Cannot resolve all parameters for \'' + stringify(typeOrFunc) + '\'(' +
signature.join(', ') + '). ' +
'Make sure that all the parameters are decorated with Inject or have valid type annotations and that \'' +
stringify(typeOrFunc) + '\' is decorated with Injectable.');
}
/**
* Thrown when getting an object by index.
*
* ### Example ([live demo](http://plnkr.co/edit/bRs0SX2OTQiJzqvjgl8P?p=preview))
*
* ```typescript
* class A {}
*
* var injector = Injector.resolveAndCreate([A]);
*
* expect(() => injector.getAt(100)).toThrowError();
* ```
* @stable
*/
export function outOfBoundsError(index: number) {
return Error(`Index ${index} is out-of-bounds.`);
}
// TODO: add a working example after alpha38 is released
/**
* Thrown when a multi provider and a regular provider are bound to the same token.
*
* ### Example
*
* ```typescript
* expect(() => Injector.resolveAndCreate([
* { provide: "Strings", useValue: "string1", multi: true},
* { provide: "Strings", useValue: "string2", multi: false}
* ])).toThrowError();
* ```
*/
export function mixingMultiProvidersWithRegularProvidersError(
provider1: any, provider2: any): Error {
return Error(`Cannot mix multi providers and regular providers, got: ${provider1} ${provider2}`);
}

View File

@ -0,0 +1,474 @@
/**
* @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 {Injector, THROW_IF_NOT_FOUND} from './injector';
import {Self, SkipSelf} from './metadata';
import {Provider} from './provider';
import {cyclicDependencyError, instantiationError, noProviderError, outOfBoundsError} from './reflective_errors';
import {ReflectiveKey} from './reflective_key';
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider';
// Threshold for the dynamic version
const UNDEFINED = new Object();
/**
* A ReflectiveDependency injection container used for instantiating objects and resolving
* dependencies.
*
* An `Injector` is a replacement for a `new` operator, which can automatically resolve the
* constructor dependencies.
*
* In typical use, application code asks for the dependencies in the constructor and they are
* resolved by the `Injector`.
*
* ### Example ([live demo](http://plnkr.co/edit/jzjec0?p=preview))
*
* The following example creates an `Injector` configured to create `Engine` and `Car`.
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var injector = ReflectiveInjector.resolveAndCreate([Car, Engine]);
* var car = injector.get(Car);
* expect(car instanceof Car).toBe(true);
* expect(car.engine instanceof Engine).toBe(true);
* ```
*
* Notice, we don't use the `new` operator because we explicitly want to have the `Injector`
* resolve all of the object's dependencies automatically.
*
* @stable
*/
export abstract class ReflectiveInjector implements Injector {
/**
* Turns an array of provider definitions into an array of resolved providers.
*
* A resolution is a process of flattening multiple nested arrays and converting individual
* providers into an array of {@link ResolvedReflectiveProvider}s.
*
* ### Example ([live demo](http://plnkr.co/edit/AiXTHi?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var providers = ReflectiveInjector.resolve([Car, [[Engine]]]);
*
* expect(providers.length).toEqual(2);
*
* expect(providers[0] instanceof ResolvedReflectiveProvider).toBe(true);
* expect(providers[0].key.displayName).toBe("Car");
* expect(providers[0].dependencies.length).toEqual(1);
* expect(providers[0].factory).toBeDefined();
*
* expect(providers[1].key.displayName).toBe("Engine");
* });
* ```
*
* See {@link ReflectiveInjector#fromResolvedProviders} for more info.
*/
static resolve(providers: Provider[]): ResolvedReflectiveProvider[] {
return resolveReflectiveProviders(providers);
}
/**
* Resolves an array of providers and creates an injector from those providers.
*
* The passed-in providers can be an array of `Type`, {@link Provider},
* or a recursive array of more providers.
*
* ### Example ([live demo](http://plnkr.co/edit/ePOccA?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var injector = ReflectiveInjector.resolveAndCreate([Car, Engine]);
* expect(injector.get(Car) instanceof Car).toBe(true);
* ```
*
* This function is slower than the corresponding `fromResolvedProviders`
* because it needs to resolve the passed-in providers first.
* See {@link Injector#resolve} and {@link Injector#fromResolvedProviders}.
*/
static resolveAndCreate(providers: Provider[], parent: Injector = null): ReflectiveInjector {
const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
return ReflectiveInjector.fromResolvedProviders(ResolvedReflectiveProviders, parent);
}
/**
* Creates an injector from previously resolved providers.
*
* This API is the recommended way to construct injectors in performance-sensitive parts.
*
* ### Example ([live demo](http://plnkr.co/edit/KrSMci?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var providers = ReflectiveInjector.resolve([Car, Engine]);
* var injector = ReflectiveInjector.fromResolvedProviders(providers);
* expect(injector.get(Car) instanceof Car).toBe(true);
* ```
* @experimental
*/
static fromResolvedProviders(providers: ResolvedReflectiveProvider[], parent: Injector = null):
ReflectiveInjector {
return new ReflectiveInjector_(providers, parent);
}
/**
* Parent of this injector.
*
* <!-- TODO: Add a link to the section of the user guide talking about hierarchical injection.
* -->
*
* ### Example ([live demo](http://plnkr.co/edit/eosMGo?p=preview))
*
* ```typescript
* var parent = ReflectiveInjector.resolveAndCreate([]);
* var child = parent.resolveAndCreateChild([]);
* expect(child.parent).toBe(parent);
* ```
*/
abstract get parent(): Injector;
/**
* Resolves an array of providers and creates a child injector from those providers.
*
* <!-- TODO: Add a link to the section of the user guide talking about hierarchical injection.
* -->
*
* The passed-in providers can be an array of `Type`, {@link Provider},
* or a recursive array of more providers.
*
* ### Example ([live demo](http://plnkr.co/edit/opB3T4?p=preview))
*
* ```typescript
* class ParentProvider {}
* class ChildProvider {}
*
* var parent = ReflectiveInjector.resolveAndCreate([ParentProvider]);
* var child = parent.resolveAndCreateChild([ChildProvider]);
*
* expect(child.get(ParentProvider) instanceof ParentProvider).toBe(true);
* expect(child.get(ChildProvider) instanceof ChildProvider).toBe(true);
* expect(child.get(ParentProvider)).toBe(parent.get(ParentProvider));
* ```
*
* This function is slower than the corresponding `createChildFromResolved`
* because it needs to resolve the passed-in providers first.
* See {@link Injector#resolve} and {@link Injector#createChildFromResolved}.
*/
abstract resolveAndCreateChild(providers: Provider[]): ReflectiveInjector;
/**
* Creates a child injector from previously resolved providers.
*
* <!-- TODO: Add a link to the section of the user guide talking about hierarchical injection.
* -->
*
* This API is the recommended way to construct injectors in performance-sensitive parts.
*
* ### Example ([live demo](http://plnkr.co/edit/VhyfjN?p=preview))
*
* ```typescript
* class ParentProvider {}
* class ChildProvider {}
*
* var parentProviders = ReflectiveInjector.resolve([ParentProvider]);
* var childProviders = ReflectiveInjector.resolve([ChildProvider]);
*
* var parent = ReflectiveInjector.fromResolvedProviders(parentProviders);
* var child = parent.createChildFromResolved(childProviders);
*
* expect(child.get(ParentProvider) instanceof ParentProvider).toBe(true);
* expect(child.get(ChildProvider) instanceof ChildProvider).toBe(true);
* expect(child.get(ParentProvider)).toBe(parent.get(ParentProvider));
* ```
*/
abstract createChildFromResolved(providers: ResolvedReflectiveProvider[]): ReflectiveInjector;
/**
* Resolves a provider and instantiates an object in the context of the injector.
*
* The created object does not get cached by the injector.
*
* ### Example ([live demo](http://plnkr.co/edit/yvVXoB?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var injector = ReflectiveInjector.resolveAndCreate([Engine]);
*
* var car = injector.resolveAndInstantiate(Car);
* expect(car.engine).toBe(injector.get(Engine));
* expect(car).not.toBe(injector.resolveAndInstantiate(Car));
* ```
*/
abstract resolveAndInstantiate(provider: Provider): any;
/**
* Instantiates an object using a resolved provider in the context of the injector.
*
* The created object does not get cached by the injector.
*
* ### Example ([live demo](http://plnkr.co/edit/ptCImQ?p=preview))
*
* ```typescript
* @Injectable()
* class Engine {
* }
*
* @Injectable()
* class Car {
* constructor(public engine:Engine) {}
* }
*
* var injector = ReflectiveInjector.resolveAndCreate([Engine]);
* var carProvider = ReflectiveInjector.resolve([Car])[0];
* var car = injector.instantiateResolved(carProvider);
* expect(car.engine).toBe(injector.get(Engine));
* expect(car).not.toBe(injector.instantiateResolved(carProvider));
* ```
*/
abstract instantiateResolved(provider: ResolvedReflectiveProvider): any;
abstract get(token: any, notFoundValue?: any): any;
}
export class ReflectiveInjector_ implements ReflectiveInjector {
/** @internal */
_constructionCounter: number = 0;
/** @internal */
public _providers: ResolvedReflectiveProvider[];
/** @internal */
public _parent: Injector;
keyIds: number[];
objs: any[];
/**
* Private
*/
constructor(_providers: ResolvedReflectiveProvider[], _parent: Injector = null) {
this._providers = _providers;
this._parent = _parent;
const len = _providers.length;
this.keyIds = new Array(len);
this.objs = new Array(len);
for (let i = 0; i < len; i++) {
this.keyIds[i] = _providers[i].key.id;
this.objs[i] = UNDEFINED;
}
}
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
return this._getByKey(ReflectiveKey.get(token), null, notFoundValue);
}
get parent(): Injector { return this._parent; }
resolveAndCreateChild(providers: Provider[]): ReflectiveInjector {
const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
return this.createChildFromResolved(ResolvedReflectiveProviders);
}
createChildFromResolved(providers: ResolvedReflectiveProvider[]): ReflectiveInjector {
const inj = new ReflectiveInjector_(providers);
inj._parent = this;
return inj;
}
resolveAndInstantiate(provider: Provider): any {
return this.instantiateResolved(ReflectiveInjector.resolve([provider])[0]);
}
instantiateResolved(provider: ResolvedReflectiveProvider): any {
return this._instantiateProvider(provider);
}
getProviderAtIndex(index: number): ResolvedReflectiveProvider {
if (index < 0 || index >= this._providers.length) {
throw outOfBoundsError(index);
}
return this._providers[index];
}
/** @internal */
_new(provider: ResolvedReflectiveProvider): any {
if (this._constructionCounter++ > this._getMaxNumberOfObjects()) {
throw cyclicDependencyError(this, provider.key);
}
return this._instantiateProvider(provider);
}
private _getMaxNumberOfObjects(): number { return this.objs.length; }
private _instantiateProvider(provider: ResolvedReflectiveProvider): any {
if (provider.multiProvider) {
const res = new Array(provider.resolvedFactories.length);
for (let i = 0; i < provider.resolvedFactories.length; ++i) {
res[i] = this._instantiate(provider, provider.resolvedFactories[i]);
}
return res;
} else {
return this._instantiate(provider, provider.resolvedFactories[0]);
}
}
private _instantiate(
provider: ResolvedReflectiveProvider,
ResolvedReflectiveFactory: ResolvedReflectiveFactory): any {
const factory = ResolvedReflectiveFactory.factory;
let deps: any[];
try {
deps =
ResolvedReflectiveFactory.dependencies.map(dep => this._getByReflectiveDependency(dep));
} catch (e) {
if (e.addKey) {
e.addKey(this, provider.key);
}
throw e;
}
let obj: any;
try {
obj = factory(...deps);
} catch (e) {
throw instantiationError(this, e, e.stack, provider.key);
}
return obj;
}
private _getByReflectiveDependency(dep: ReflectiveDependency): any {
return this._getByKey(dep.key, dep.visibility, dep.optional ? null : THROW_IF_NOT_FOUND);
}
private _getByKey(key: ReflectiveKey, visibility: Self|SkipSelf, notFoundValue: any): any {
if (key === INJECTOR_KEY) {
return this;
}
if (visibility instanceof Self) {
return this._getByKeySelf(key, notFoundValue);
} else {
return this._getByKeyDefault(key, notFoundValue, visibility);
}
}
private _getObjByKeyId(keyId: number): any {
for (let i = 0; i < this.keyIds.length; i++) {
if (this.keyIds[i] === keyId) {
if (this.objs[i] === UNDEFINED) {
this.objs[i] = this._new(this._providers[i]);
}
return this.objs[i];
}
}
return UNDEFINED;
}
/** @internal */
_throwOrNull(key: ReflectiveKey, notFoundValue: any): any {
if (notFoundValue !== THROW_IF_NOT_FOUND) {
return notFoundValue;
} else {
throw noProviderError(this, key);
}
}
/** @internal */
_getByKeySelf(key: ReflectiveKey, notFoundValue: any): any {
const obj = this._getObjByKeyId(key.id);
return (obj !== UNDEFINED) ? obj : this._throwOrNull(key, notFoundValue);
}
/** @internal */
_getByKeyDefault(key: ReflectiveKey, notFoundValue: any, visibility: Self|SkipSelf): any {
let inj: Injector;
if (visibility instanceof SkipSelf) {
inj = this._parent;
} else {
inj = this;
}
while (inj instanceof ReflectiveInjector_) {
const inj_ = <ReflectiveInjector_>inj;
const obj = inj_._getObjByKeyId(key.id);
if (obj !== UNDEFINED) return obj;
inj = inj_._parent;
}
if (inj !== null) {
return inj.get(key.token, notFoundValue);
} else {
return this._throwOrNull(key, notFoundValue);
}
}
get displayName(): string {
const providers =
_mapProviders(this, (b: ResolvedReflectiveProvider) => ' "' + b.key.displayName + '" ')
.join(', ');
return `ReflectiveInjector(providers: [${providers}])`;
}
toString(): string { return this.displayName; }
}
const INJECTOR_KEY = ReflectiveKey.get(Injector);
function _mapProviders(injector: ReflectiveInjector_, fn: Function): any[] {
const res: any[] = new Array(injector._providers.length);
for (let i = 0; i < injector._providers.length; ++i) {
res[i] = fn(injector.getProviderAtIndex(i));
}
return res;
}

View File

@ -0,0 +1,78 @@
/**
* @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 {stringify} from '../util';
import {resolveForwardRef} from './forward_ref';
/**
* A unique object used for retrieving items from the {@link ReflectiveInjector}.
*
* Keys have:
* - a system-wide unique `id`.
* - a `token`.
*
* `Key` is used internally by {@link ReflectiveInjector} because its system-wide unique `id` allows
* the
* injector to store created objects in a more efficient way.
*
* `Key` should not be created directly. {@link ReflectiveInjector} creates keys automatically when
* resolving
* providers.
* @experimental
*/
export class ReflectiveKey {
/**
* Private
*/
constructor(public token: Object, public id: number) {
if (!token) {
throw new Error('Token must be defined!');
}
}
/**
* Returns a stringified token.
*/
get displayName(): string { return stringify(this.token); }
/**
* Retrieves a `Key` for a token.
*/
static get(token: Object): ReflectiveKey {
return _globalKeyRegistry.get(resolveForwardRef(token));
}
/**
* @returns the number of keys registered in the system.
*/
static get numberOfKeys(): number { return _globalKeyRegistry.numberOfKeys; }
}
/**
* @internal
*/
export class KeyRegistry {
private _allKeys = new Map<Object, ReflectiveKey>();
get(token: Object): ReflectiveKey {
if (token instanceof ReflectiveKey) return token;
if (this._allKeys.has(token)) {
return this._allKeys.get(token);
}
const newKey = new ReflectiveKey(token, ReflectiveKey.numberOfKeys);
this._allKeys.set(token, newKey);
return newKey;
}
get numberOfKeys(): number { return this._allKeys.size; }
}
const _globalKeyRegistry = new KeyRegistry();

View File

@ -0,0 +1,266 @@
/**
* @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 {reflector} from '../reflection/reflection';
import {Type} from '../type';
import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token';
import {Inject, Optional, Self, SkipSelf} from './metadata';
import {ClassProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ValueProvider} from './provider';
import {invalidProviderError, mixingMultiProvidersWithRegularProvidersError, noAnnotationError} from './reflective_errors';
import {ReflectiveKey} from './reflective_key';
interface NormalizedProvider extends TypeProvider, ValueProvider, ClassProvider, ExistingProvider,
FactoryProvider {}
/**
* `Dependency` is used by the framework to extend DI.
* This is internal to Angular and should not be used directly.
*/
export class ReflectiveDependency {
constructor(
public key: ReflectiveKey, public optional: boolean, public visibility: Self|SkipSelf) {}
static fromKey(key: ReflectiveKey): ReflectiveDependency {
return new ReflectiveDependency(key, false, null);
}
}
const _EMPTY_LIST: any[] = [];
/**
* An internal resolved representation of a {@link Provider} used by the {@link Injector}.
*
* It is usually created automatically by `Injector.resolveAndCreate`.
*
* It can be created manually, as follows:
*
* ### Example ([live demo](http://plnkr.co/edit/RfEnhh8kUEI0G3qsnIeT?p%3Dpreview&p=preview))
*
* ```typescript
* var resolvedProviders = Injector.resolve([{ provide: 'message', useValue: 'Hello' }]);
* var injector = Injector.fromResolvedProviders(resolvedProviders);
*
* expect(injector.get('message')).toEqual('Hello');
* ```
*
* @experimental
*/
export interface ResolvedReflectiveProvider {
/**
* A key, usually a `Type<any>`.
*/
key: ReflectiveKey;
/**
* Factory function which can return an instance of an object represented by a key.
*/
resolvedFactories: ResolvedReflectiveFactory[];
/**
* Indicates if the provider is a multi-provider or a regular provider.
*/
multiProvider: boolean;
}
export class ResolvedReflectiveProvider_ implements ResolvedReflectiveProvider {
constructor(
public key: ReflectiveKey, public resolvedFactories: ResolvedReflectiveFactory[],
public multiProvider: boolean) {}
get resolvedFactory(): ResolvedReflectiveFactory { return this.resolvedFactories[0]; }
}
/**
* An internal resolved representation of a factory function created by resolving {@link
* Provider}.
* @experimental
*/
export class ResolvedReflectiveFactory {
constructor(
/**
* Factory function which can return an instance of an object represented by a key.
*/
public factory: Function,
/**
* Arguments (dependencies) to the `factory` function.
*/
public dependencies: ReflectiveDependency[]) {}
}
/**
* Resolve a single provider.
*/
function resolveReflectiveFactory(provider: NormalizedProvider): ResolvedReflectiveFactory {
let factoryFn: Function;
let resolvedDeps: ReflectiveDependency[];
if (provider.useClass) {
const useClass = resolveForwardRef(provider.useClass);
factoryFn = reflector.factory(useClass);
resolvedDeps = _dependenciesFor(useClass);
} else if (provider.useExisting) {
factoryFn = (aliasInstance: any) => aliasInstance;
resolvedDeps = [ReflectiveDependency.fromKey(ReflectiveKey.get(provider.useExisting))];
} else if (provider.useFactory) {
factoryFn = provider.useFactory;
resolvedDeps = constructDependencies(provider.useFactory, provider.deps);
} else {
factoryFn = () => provider.useValue;
resolvedDeps = _EMPTY_LIST;
}
return new ResolvedReflectiveFactory(factoryFn, resolvedDeps);
}
/**
* Converts the {@link Provider} into {@link ResolvedProvider}.
*
* {@link Injector} internally only uses {@link ResolvedProvider}, {@link Provider} contains
* convenience provider syntax.
*/
function resolveReflectiveProvider(provider: NormalizedProvider): ResolvedReflectiveProvider {
return new ResolvedReflectiveProvider_(
ReflectiveKey.get(provider.provide), [resolveReflectiveFactory(provider)], provider.multi);
}
/**
* Resolve a list of Providers.
*/
export function resolveReflectiveProviders(providers: Provider[]): ResolvedReflectiveProvider[] {
const normalized = _normalizeProviders(providers, []);
const resolved = normalized.map(resolveReflectiveProvider);
const resolvedProviderMap = mergeResolvedReflectiveProviders(resolved, new Map());
return Array.from(resolvedProviderMap.values());
}
/**
* Merges a list of ResolvedProviders into a list where
* each key is contained exactly once and multi providers
* have been merged.
*/
export function mergeResolvedReflectiveProviders(
providers: ResolvedReflectiveProvider[],
normalizedProvidersMap: Map<number, ResolvedReflectiveProvider>):
Map<number, ResolvedReflectiveProvider> {
for (let i = 0; i < providers.length; i++) {
const provider = providers[i];
const existing = normalizedProvidersMap.get(provider.key.id);
if (existing) {
if (provider.multiProvider !== existing.multiProvider) {
throw mixingMultiProvidersWithRegularProvidersError(existing, provider);
}
if (provider.multiProvider) {
for (let j = 0; j < provider.resolvedFactories.length; j++) {
existing.resolvedFactories.push(provider.resolvedFactories[j]);
}
} else {
normalizedProvidersMap.set(provider.key.id, provider);
}
} else {
let resolvedProvider: ResolvedReflectiveProvider;
if (provider.multiProvider) {
resolvedProvider = new ResolvedReflectiveProvider_(
provider.key, provider.resolvedFactories.slice(), provider.multiProvider);
} else {
resolvedProvider = provider;
}
normalizedProvidersMap.set(provider.key.id, resolvedProvider);
}
}
return normalizedProvidersMap;
}
function _normalizeProviders(providers: Provider[], res: Provider[]): Provider[] {
providers.forEach(b => {
if (b instanceof Type) {
res.push({provide: b, useClass: b});
} else if (b && typeof b == 'object' && (b as any).provide !== undefined) {
res.push(b as NormalizedProvider);
} else if (b instanceof Array) {
_normalizeProviders(b, res);
} else {
throw invalidProviderError(b);
}
});
return res;
}
export function constructDependencies(
typeOrFunc: any, dependencies: any[]): ReflectiveDependency[] {
if (!dependencies) {
return _dependenciesFor(typeOrFunc);
} else {
const params: any[][] = dependencies.map(t => [t]);
return dependencies.map(t => _extractToken(typeOrFunc, t, params));
}
}
function _dependenciesFor(typeOrFunc: any): ReflectiveDependency[] {
const params = reflector.parameters(typeOrFunc);
if (!params) return [];
if (params.some(p => p == null)) {
throw noAnnotationError(typeOrFunc, params);
}
return params.map(p => _extractToken(typeOrFunc, p, params));
}
function _extractToken(
typeOrFunc: any, metadata: any[] | any, params: any[][]): ReflectiveDependency {
let token: any = null;
let optional = false;
if (!Array.isArray(metadata)) {
if (metadata instanceof Inject) {
return _createDependency(metadata['token'], optional, null);
} else {
return _createDependency(metadata, optional, null);
}
}
let visibility: Self|SkipSelf = null;
for (let i = 0; i < metadata.length; ++i) {
const paramMetadata = metadata[i];
if (paramMetadata instanceof Type) {
token = paramMetadata;
} else if (paramMetadata instanceof Inject) {
token = paramMetadata['token'];
} else if (paramMetadata instanceof Optional) {
optional = true;
} else if (paramMetadata instanceof Self || paramMetadata instanceof SkipSelf) {
visibility = paramMetadata;
} else if (paramMetadata instanceof InjectionToken) {
token = paramMetadata;
}
}
token = resolveForwardRef(token);
if (token != null) {
return _createDependency(token, optional, visibility);
} else {
throw noAnnotationError(typeOrFunc, params);
}
}
function _createDependency(
token: any, optional: boolean, visibility: Self | SkipSelf): ReflectiveDependency {
return new ReflectiveDependency(ReflectiveKey.get(token), optional, visibility);
}

View File

@ -0,0 +1,125 @@
/**
* @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 {ERROR_ORIGINAL_ERROR, getDebugContext, getOriginalError} from './errors';
/**
* @whatItDoes Provides a hook for centralized exception handling.
*
* @description
*
* The default implementation of `ErrorHandler` prints error messages to the `console`. To
* intercept error handling, write a custom exception handler that replaces this default as
* appropriate for your app.
*
* ### Example
*
* ```
* class MyErrorHandler implements ErrorHandler {
* handleError(error) {
* // do something with the exception
* }
* }
*
* @NgModule({
* providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
* })
* class MyModule {}
* ```
*
* @stable
*/
export class ErrorHandler {
/**
* @internal
*/
_console: Console = console;
/**
* @internal
*/
rethrowError: boolean;
constructor(rethrowError: boolean = true) { this.rethrowError = rethrowError; }
handleError(error: any): void {
this._console.error(`EXCEPTION: ${this._extractMessage(error)}`);
if (error instanceof Error) {
const originalError = this._findOriginalError(error);
const originalStack = this._findOriginalStack(error);
const context = this._findContext(error);
if (originalError) {
this._console.error(`ORIGINAL EXCEPTION: ${this._extractMessage(originalError)}`);
}
if (originalStack) {
this._console.error('ORIGINAL STACKTRACE:');
this._console.error(originalStack);
}
if (context) {
this._console.error('ERROR CONTEXT:');
this._console.error(context);
}
}
// We rethrow exceptions, so operations like 'bootstrap' will result in an error
// when an error happens. If we do not rethrow, bootstrap will always succeed.
if (this.rethrowError) throw error;
}
/** @internal */
_extractMessage(error: any): string {
return error instanceof Error ? error.message : error.toString();
}
/** @internal */
_findContext(error: any): any {
if (error) {
return getDebugContext(error) ? getDebugContext(error) :
this._findContext(getOriginalError(error));
}
return null;
}
/** @internal */
_findOriginalError(error: Error): any {
let e = getOriginalError(error);
while (e && getOriginalError(e)) {
e = getOriginalError(e);
}
return e;
}
/** @internal */
_findOriginalStack(error: Error): string {
let e: any = error;
let stack: string = e.stack;
while (e instanceof Error && getOriginalError(e)) {
e = getOriginalError(e);
if (e instanceof Error && e.stack) {
stack = e.stack;
}
}
return stack;
}
}
export function wrappedError(message: string, originalError: any): Error {
const msg =
`${message} caused by: ${originalError instanceof Error ? originalError.message: originalError }`;
const error = Error(msg);
(error as any)[ERROR_ORIGINAL_ERROR] = originalError;
return error;
}

View File

@ -0,0 +1,27 @@
/**
* @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 {DebugContext} from './view';
export const ERROR_TYPE = 'ngType';
export const ERROR_COMPONENT_TYPE = 'ngComponentType';
export const ERROR_DEBUG_CONTEXT = 'ngDebugContext';
export const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
export function getType(error: Error): Function {
return (error as any)[ERROR_TYPE];
}
export function getDebugContext(error: Error): DebugContext {
return (error as any)[ERROR_DEBUG_CONTEXT];
}
export function getOriginalError(error: Error): Error {
return (error as any)[ERROR_ORIGINAL_ERROR];
}

View File

@ -0,0 +1,113 @@
/**
* @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 {Subject} from 'rxjs/Subject';
/**
* Use by directives and components to emit custom Events.
*
* ### Examples
*
* In the following example, `Zippy` alternatively emits `open` and `close` events when its
* title gets clicked:
*
* ```
* @Component({
* selector: 'zippy',
* template: `
* <div class="zippy">
* <div (click)="toggle()">Toggle</div>
* <div [hidden]="!visible">
* <ng-content></ng-content>
* </div>
* </div>`})
* export class Zippy {
* visible: boolean = true;
* @Output() open: EventEmitter<any> = new EventEmitter();
* @Output() close: EventEmitter<any> = new EventEmitter();
*
* toggle() {
* this.visible = !this.visible;
* if (this.visible) {
* this.open.emit(null);
* } else {
* this.close.emit(null);
* }
* }
* }
* ```
*
* The events payload can be accessed by the parameter `$event` on the components output event
* handler:
*
* ```
* <zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>
* ```
*
* Uses Rx.Observable but provides an adapter to make it work as specified here:
* https://github.com/jhusain/observable-spec
*
* Once a reference implementation of the spec is available, switch to it.
* @stable
*/
export class EventEmitter<T> extends Subject<T> {
// TODO: mark this as internal once all the facades are gone
// we can't mark it as internal now because EventEmitter exported via @angular/core would not
// contain this property making it incompatible with all the code that uses EventEmitter via
// facades, which are local to the code and do not have this property stripped.
// tslint:disable-next-line
__isAsync: boolean;
/**
* Creates an instance of [EventEmitter], which depending on [isAsync],
* delivers events synchronously or asynchronously.
*/
constructor(isAsync: boolean = false) {
super();
this.__isAsync = isAsync;
}
emit(value?: T) { super.next(value); }
subscribe(generatorOrNext?: any, error?: any, complete?: any): any {
let schedulerFn: (t: any) => any;
let errorFn = (err: any): any => null;
let completeFn = (): any => null;
if (generatorOrNext && typeof generatorOrNext === 'object') {
schedulerFn = this.__isAsync ? (value: any) => {
setTimeout(() => generatorOrNext.next(value));
} : (value: any) => { generatorOrNext.next(value); };
if (generatorOrNext.error) {
errorFn = this.__isAsync ? (err) => { setTimeout(() => generatorOrNext.error(err)); } :
(err) => { generatorOrNext.error(err); };
}
if (generatorOrNext.complete) {
completeFn = this.__isAsync ? () => { setTimeout(() => generatorOrNext.complete()); } :
() => { generatorOrNext.complete(); };
}
} else {
schedulerFn = this.__isAsync ? (value: any) => { setTimeout(() => generatorOrNext(value)); } :
(value: any) => { generatorOrNext(value); };
if (error) {
errorFn =
this.__isAsync ? (err) => { setTimeout(() => error(err)); } : (err) => { error(err); };
}
if (complete) {
completeFn =
this.__isAsync ? () => { setTimeout(() => complete()); } : () => { complete(); };
}
}
return super.subscribe(schedulerFn, errorFn, completeFn);
}
}

View File

@ -0,0 +1,33 @@
/**
* @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 {InjectionToken} from '../di/injection_token';
/**
* @experimental i18n support is experimental.
*/
export const LOCALE_ID = new InjectionToken<string>('LocaleId');
/**
* @experimental i18n support is experimental.
*/
export const TRANSLATIONS = new InjectionToken<string>('Translations');
/**
* @experimental i18n support is experimental.
*/
export const TRANSLATIONS_FORMAT = new InjectionToken<string>('TranslationsFormat');
/**
* @experimental i18n support is experimental.
*/
export enum MissingTranslationStrategy {
Error,
Warning,
Ignore,
}

View File

@ -0,0 +1,20 @@
/**
* @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
*/
// Public API for compiler
export {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, ModuleWithComponentFactories} from './linker/compiler';
export {ComponentFactory, ComponentRef} from './linker/component_factory';
export {ComponentFactoryResolver} from './linker/component_factory_resolver';
export {ElementRef} from './linker/element_ref';
export {NgModuleFactory, NgModuleRef} from './linker/ng_module_factory';
export {NgModuleFactoryLoader, getModuleFactory} from './linker/ng_module_factory_loader';
export {QueryList} from './linker/query_list';
export {SystemJsNgModuleLoader, SystemJsNgModuleLoaderConfig} from './linker/system_js_ng_module_factory_loader';
export {TemplateRef} from './linker/template_ref';
export {ViewContainerRef} from './linker/view_container_ref';
export {EmbeddedViewRef, ViewRef} from './linker/view_ref';

View File

@ -0,0 +1,123 @@
/**
* @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 {Injectable, InjectionToken} from '../di';
import {MissingTranslationStrategy} from '../i18n/tokens';
import {ViewEncapsulation} from '../metadata';
import {Type} from '../type';
import {ComponentFactory} from './component_factory';
import {NgModuleFactory} from './ng_module_factory';
/**
* Combination of NgModuleFactory and ComponentFactorys.
*
* @experimental
*/
export class ModuleWithComponentFactories<T> {
constructor(
public ngModuleFactory: NgModuleFactory<T>,
public componentFactories: ComponentFactory<any>[]) {}
}
function _throwError() {
throw new Error(`Runtime compiler is not loaded`);
}
/**
* Low-level service for running the angular compiler during runtime
* to create {@link ComponentFactory}s, which
* can later be used to create and render a Component instance.
*
* Each `@NgModule` provides an own `Compiler` to its injector,
* that will use the directives/pipes of the ng module for compilation
* of components.
* @stable
*/
@Injectable()
export class Compiler {
/**
* Compiles the given NgModule and all of its components. All templates of the components listed
* in `entryComponents` have to be inlined.
*/
compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T> { throw _throwError(); }
/**
* Compiles the given NgModule and all of its components
*/
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> { throw _throwError(); }
/**
* Same as {@link compileModuleSync} but also creates ComponentFactories for all components.
*/
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> {
throw _throwError();
}
/**
* Same as {@link compileModuleAsync} but also creates ComponentFactories for all components.
*/
compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>):
Promise<ModuleWithComponentFactories<T>> {
throw _throwError();
}
/**
* Exposes the CSS-style selectors that have been used in `ngContent` directives within
* the template of the given component.
* This is used by the `upgrade` library to compile the appropriate transclude content
* in the AngularJS wrapper component.
*/
getNgContentSelectors(component: Type<any>): string[] { throw _throwError(); }
/**
* Clears all caches.
*/
clearCache(): void {}
/**
* Clears the cache for the given component/ngModule.
*/
clearCacheFor(type: Type<any>) {}
}
/**
* Options for creating a compiler
*
* @experimental
*/
export type CompilerOptions = {
/**
* @deprecated since v4 this option has no effect anymore.
*/
useDebug?: boolean,
useJit?: boolean,
defaultEncapsulation?: ViewEncapsulation,
providers?: any[],
missingTranslation?: MissingTranslationStrategy,
// Whether to support the `<template>` tag and the `template` attribute to define angular
// templates. They have been deprecated in 4.x, `<ng-template>` should be used instead.
enableLegacyTemplate?: boolean,
};
/**
* Token to provide CompilerOptions in the platform injector.
*
* @experimental
*/
export const COMPILER_OPTIONS = new InjectionToken<CompilerOptions[]>('compilerOptions');
/**
* A factory for creating a Compiler
*
* @experimental
*/
export abstract class CompilerFactory {
abstract createCompiler(options?: CompilerOptions[]): Compiler;
}

View File

@ -0,0 +1,77 @@
/**
* @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} from '../change_detection/change_detection';
import {Injector} from '../di/injector';
import {Type} from '../type';
import {ElementRef} from './element_ref';
import {ViewRef} from './view_ref';
/**
* Represents an instance of a Component created via a {@link ComponentFactory}.
*
* `ComponentRef` provides access to the Component Instance as well other objects related to this
* Component Instance and allows you to destroy the Component Instance via the {@link #destroy}
* method.
* @stable
*/
export abstract class ComponentRef<C> {
/**
* Location of the Host Element of this Component Instance.
*/
abstract get location(): ElementRef;
/**
* The injector on which the component instance exists.
*/
abstract get injector(): Injector;
/**
* The instance of the Component.
*/
abstract get instance(): C;
/**
* The {@link ViewRef} of the Host View of this Component instance.
*/
abstract get hostView(): ViewRef;
/**
* The {@link ChangeDetectorRef} of the Component instance.
*/
abstract get changeDetectorRef(): ChangeDetectorRef;
/**
* The component type.
*/
abstract get componentType(): Type<any>;
/**
* Destroys the component instance and all of the data structures associated with it.
*/
abstract destroy(): void;
/**
* Allows to register a callback that will be called when the component is destroyed.
*/
abstract onDestroy(callback: Function): void;
}
/**
* @stable
*/
export abstract class ComponentFactory<C> {
abstract get selector(): string;
abstract get componentType(): Type<any>;
/**
* Creates a new component.
*/
abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any):
ComponentRef<C>;
}

View File

@ -0,0 +1,61 @@
/**
* @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 {Type} from '../type';
import {stringify} from '../util';
import {ComponentFactory} from './component_factory';
export function noComponentFactoryError(component: Function) {
const error = Error(
`No component factory found for ${stringify(component)}. Did you add it to @NgModule.entryComponents?`);
(error as any)[ERROR_COMPONENT] = component;
return error;
}
const ERROR_COMPONENT = 'ngComponent';
export function getComponent(error: Error): Type<any> {
return (error as any)[ERROR_COMPONENT];
}
class _NullComponentFactoryResolver implements ComponentFactoryResolver {
resolveComponentFactory<T>(component: {new (...args: any[]): T}): ComponentFactory<T> {
throw noComponentFactoryError(component);
}
}
/**
* @stable
*/
export abstract class ComponentFactoryResolver {
static NULL: ComponentFactoryResolver = new _NullComponentFactoryResolver();
abstract resolveComponentFactory<T>(component: Type<T>): ComponentFactory<T>;
}
export class CodegenComponentFactoryResolver implements ComponentFactoryResolver {
private _factories = new Map<any, ComponentFactory<any>>();
constructor(factories: ComponentFactory<any>[], private _parent: ComponentFactoryResolver) {
for (let i = 0; i < factories.length; i++) {
const factory = factories[i];
this._factories.set(factory.componentType, factory);
}
}
resolveComponentFactory<T>(component: {new (...args: any[]): T}): ComponentFactory<T> {
let result = this._factories.get(component);
if (!result) {
result = this._parent.resolveComponentFactory(component);
}
return result;
}
}

View File

@ -0,0 +1,48 @@
/**
* @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
*/
/**
* A wrapper around a native element inside of a View.
*
* An `ElementRef` is backed by a render-specific element. In the browser, this is usually a DOM
* element.
*
* @security Permitting direct access to the DOM can make your application more vulnerable to
* XSS attacks. Carefully review any use of `ElementRef` in your code. For more detail, see the
* [Security Guide](http://g.co/ng/security).
*
* @stable
*/
// Note: We don't expose things like `Injector`, `ViewContainer`, ... here,
// i.e. users have to ask for what they need. With that, we can build better analysis tools
// and could do better codegen in the future.
export class ElementRef {
/**
* The underlying native element or `null` if direct access to native elements is not supported
* (e.g. when the application runs in a web worker).
*
* <div class="callout is-critical">
* <header>Use with caution</header>
* <p>
* Use this API as the last resort when direct access to DOM is needed. Use templating and
* data-binding provided by Angular instead. Alternatively you take a look at {@link Renderer}
* which provides API that can safely be used even when direct access to native elements is not
* supported.
* </p>
* <p>
* Relying on direct DOM access creates tight coupling between your application and rendering
* layers which will make it impossible to separate the two and deploy your application into a
* web worker.
* </p>
* </div>
* @stable
*/
public nativeElement: any;
constructor(nativeElement: any) { this.nativeElement = nativeElement; }
}

View File

@ -0,0 +1,121 @@
/**
* @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 {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {Type} from '../type';
import {stringify} from '../util';
import {ComponentFactory} from './component_factory';
import {CodegenComponentFactoryResolver, ComponentFactoryResolver} from './component_factory_resolver';
/**
* Represents an instance of an NgModule created via a {@link NgModuleFactory}.
*
* `NgModuleRef` provides access to the NgModule Instance as well other objects related to this
* NgModule Instance.
*
* @stable
*/
export abstract class NgModuleRef<T> {
/**
* The injector that contains all of the providers of the NgModule.
*/
abstract get injector(): Injector;
/**
* The ComponentFactoryResolver to get hold of the ComponentFactories
* declared in the `entryComponents` property of the module.
*/
abstract get componentFactoryResolver(): ComponentFactoryResolver;
/**
* The NgModule instance.
*/
abstract get instance(): T;
/**
* Destroys the module instance and all of the data structures associated with it.
*/
abstract destroy(): void;
/**
* Allows to register a callback that will be called when the module is destroyed.
*/
abstract onDestroy(callback: () => void): void;
}
/**
* @experimental
*/
export class NgModuleFactory<T> {
constructor(
private _injectorClass: {new (parentInjector: Injector): NgModuleInjector<T>},
private _moduleType: Type<T>) {}
get moduleType(): Type<T> { return this._moduleType; }
create(parentInjector: Injector): NgModuleRef<T> {
if (!parentInjector) {
parentInjector = Injector.NULL;
}
const instance = new this._injectorClass(parentInjector);
instance.create();
return instance;
}
}
const _UNDEFINED = new Object();
export abstract class NgModuleInjector<T> extends CodegenComponentFactoryResolver implements
Injector,
NgModuleRef<T> {
private _destroyListeners: (() => void)[] = [];
private _destroyed: boolean = false;
public instance: T;
constructor(
public parent: Injector, factories: ComponentFactory<any>[],
public bootstrapFactories: ComponentFactory<any>[]) {
super(factories, parent.get(ComponentFactoryResolver, ComponentFactoryResolver.NULL));
}
create() { this.instance = this.createInternal(); }
abstract createInternal(): T;
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
if (token === Injector || token === ComponentFactoryResolver) {
return this;
}
const result = this.getInternal(token, _UNDEFINED);
return result === _UNDEFINED ? this.parent.get(token, notFoundValue) : result;
}
abstract getInternal(token: any, notFoundValue: any): any;
get injector(): Injector { return this; }
get componentFactoryResolver(): ComponentFactoryResolver { return this; }
destroy(): void {
if (this._destroyed) {
throw new Error(
`The ng module ${stringify(this.instance.constructor)} has already been destroyed.`);
}
this._destroyed = true;
this.destroyInternal();
this._destroyListeners.forEach((listener) => listener());
}
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
abstract destroyInternal(): void;
}

View File

@ -0,0 +1,48 @@
/**
* @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 {NgModuleFactory} from './ng_module_factory';
/**
* Used to load ng module factories.
* @stable
*/
export abstract class NgModuleFactoryLoader {
abstract load(path: string): Promise<NgModuleFactory<any>>;
}
let moduleFactories = new Map<string, NgModuleFactory<any>>();
/**
* Registers a loaded module. Should only be called from generated NgModuleFactory code.
* @experimental
*/
export function registerModuleFactory(id: string, factory: NgModuleFactory<any>) {
const existing = moduleFactories.get(id);
if (existing) {
throw new Error(`Duplicate module registered for ${id
} - ${existing.moduleType.name} vs ${factory.moduleType.name}`);
}
moduleFactories.set(id, factory);
}
export function clearModulesForTest() {
moduleFactories = new Map<string, NgModuleFactory<any>>();
}
/**
* Returns the NgModuleFactory with the given id, if it exists and has been loaded.
* Factories for modules that do not specify an `id` cannot be retrieved. Throws if the module
* cannot be found.
* @experimental
*/
export function getModuleFactory(id: string): NgModuleFactory<any> {
const factory = moduleFactories.get(id);
if (!factory) throw new Error(`No module with ID ${id} loaded`);
return factory;
}

View File

@ -0,0 +1,115 @@
/**
* @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 {Observable} from 'rxjs/Observable';
import {EventEmitter} from '../event_emitter';
import {getSymbolIterator} from '../util';
/**
* An unmodifiable list of items that Angular keeps up to date when the state
* of the application changes.
*
* The type of object that {@link Query} and {@link ViewQueryMetadata} provide.
*
* Implements an iterable interface, therefore it can be used in both ES6
* javascript `for (var i of items)` loops as well as in Angular templates with
* `*ngFor="let i of myList"`.
*
* Changes can be observed by subscribing to the changes `Observable`.
*
* NOTE: In the future this class will implement an `Observable` interface.
*
* ### Example ([live demo](http://plnkr.co/edit/RX8sJnQYl9FWuSCWme5z?p=preview))
* ```typescript
* @Component({...})
* class Container {
* @ViewChildren(Item) items:QueryList<Item>;
* }
* ```
* @stable
*/
export class QueryList<T>/* implements Iterable<T> */ {
private _dirty = true;
private _results: Array<T> = [];
private _emitter = new EventEmitter();
get changes(): Observable<any> { return this._emitter; }
get length(): number { return this._results.length; }
get first(): T { return this._results[0]; }
get last(): T { return this._results[this.length - 1]; }
/**
* See
* [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
*/
map<U>(fn: (item: T, index: number, array: T[]) => U): U[] { return this._results.map(fn); }
/**
* See
* [Array.filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
*/
filter(fn: (item: T, index: number, array: T[]) => boolean): T[] {
return this._results.filter(fn);
}
/**
* See
* [Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
*/
find(fn: (item: T, index: number, array: T[]) => boolean): T { return this._results.find(fn); }
/**
* See
* [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)
*/
reduce<U>(fn: (prevValue: U, curValue: T, curIndex: number, array: T[]) => U, init: U): U {
return this._results.reduce(fn, init);
}
/**
* See
* [Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
*/
forEach(fn: (item: T, index: number, array: T[]) => void): void { this._results.forEach(fn); }
/**
* See
* [Array.some](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)
*/
some(fn: (value: T, index: number, array: T[]) => boolean): boolean {
return this._results.some(fn);
}
toArray(): T[] { return this._results.slice(); }
[getSymbolIterator()](): Iterator<T> { return (this._results as any)[getSymbolIterator()](); }
toString(): string { return this._results.toString(); }
reset(res: Array<T|any[]>): void {
this._results = flatten(res);
this._dirty = false;
}
notifyOnChanges(): void { this._emitter.emit(this); }
/** internal */
setDirty() { this._dirty = true; }
/** internal */
get dirty() { return this._dirty; }
}
function flatten<T>(list: Array<T|T[]>): T[] {
return list.reduce((flat: any[], item: T | T[]): T[] => {
const flatItem = Array.isArray(item) ? flatten(item) : item;
return (<T[]>flat).concat(flatItem);
}, []);
}

View File

@ -0,0 +1,91 @@
/**
* @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 {Injectable, Optional} from '../di';
import {Compiler} from './compiler';
import {NgModuleFactory} from './ng_module_factory';
import {NgModuleFactoryLoader} from './ng_module_factory_loader';
const _SEPARATOR = '#';
const FACTORY_CLASS_SUFFIX = 'NgFactory';
/**
* Configuration for SystemJsNgModuleLoader.
* token.
*
* @experimental
*/
export abstract class SystemJsNgModuleLoaderConfig {
/**
* Prefix to add when computing the name of the factory module for a given module name.
*/
factoryPathPrefix: string;
/**
* Suffix to add when computing the name of the factory module for a given module name.
*/
factoryPathSuffix: string;
}
const DEFAULT_CONFIG: SystemJsNgModuleLoaderConfig = {
factoryPathPrefix: '',
factoryPathSuffix: '.ngfactory',
};
/**
* NgModuleFactoryLoader that uses SystemJS to load NgModuleFactory
* @experimental
*/
@Injectable()
export class SystemJsNgModuleLoader implements NgModuleFactoryLoader {
private _config: SystemJsNgModuleLoaderConfig;
constructor(private _compiler: Compiler, @Optional() config?: SystemJsNgModuleLoaderConfig) {
this._config = config || DEFAULT_CONFIG;
}
load(path: string): Promise<NgModuleFactory<any>> {
const offlineMode = this._compiler instanceof Compiler;
return offlineMode ? this.loadFactory(path) : this.loadAndCompile(path);
}
private loadAndCompile(path: string): Promise<NgModuleFactory<any>> {
let [module, exportName] = path.split(_SEPARATOR);
if (exportName === undefined) {
exportName = 'default';
}
return System.import(module)
.then((module: any) => module[exportName])
.then((type: any) => checkNotEmpty(type, module, exportName))
.then((type: any) => this._compiler.compileModuleAsync(type));
}
private loadFactory(path: string): Promise<NgModuleFactory<any>> {
let [module, exportName] = path.split(_SEPARATOR);
let factoryClassSuffix = FACTORY_CLASS_SUFFIX;
if (exportName === undefined) {
exportName = 'default';
factoryClassSuffix = '';
}
return System.import(this._config.factoryPathPrefix + module + this._config.factoryPathSuffix)
.then((module: any) => module[exportName + factoryClassSuffix])
.then((factory: any) => checkNotEmpty(factory, module, exportName));
}
}
function checkNotEmpty(value: any, modulePath: string, exportName: string): any {
if (!value) {
throw new Error(`Cannot find '${exportName}' in '${modulePath}'`);
}
return value;
}

View File

@ -0,0 +1,42 @@
/**
* @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 {ElementRef} from './element_ref';
import {EmbeddedViewRef} from './view_ref';
/**
* Represents an Embedded Template that can be used to instantiate Embedded Views.
*
* You can access a `TemplateRef`, in two ways. Via a directive placed on a `<ng-template>` element
* (or directive prefixed with `*`) and have the `TemplateRef` for this Embedded View injected into
* the constructor of the directive using the `TemplateRef` Token. Alternatively you can query for
* the `TemplateRef` from a Component or a Directive via {@link Query}.
*
* To instantiate Embedded Views based on a Template, use
* {@link ViewContainerRef#createEmbeddedView}, which will create the View and attach it to the
* View Container.
* @stable
*/
export abstract class TemplateRef<C> {
/**
* The location in the View where the Embedded View logically belongs to.
*
* The data-binding and injection contexts of Embedded Views created from this `TemplateRef`
* inherit from the contexts of this location.
*
* Typically new Embedded Views are attached to the View Container of this location, but in
* advanced use-cases, the View can be attached to a different container while keeping the
* data-binding and injection context from the original location.
*
*/
// TODO(i): rename to anchor or location
abstract get elementRef(): ElementRef;
abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
}

View File

@ -0,0 +1,123 @@
/**
* @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 {Injector} from '../di/injector';
import {ComponentFactory, ComponentRef} from './component_factory';
import {ElementRef} from './element_ref';
import {TemplateRef} from './template_ref';
import {EmbeddedViewRef, ViewRef} from './view_ref';
/**
* Represents a container where one or more Views can be attached.
*
* The container can contain two kinds of Views. Host Views, created by instantiating a
* {@link Component} via {@link #createComponent}, and Embedded Views, created by instantiating an
* {@link TemplateRef Embedded Template} via {@link #createEmbeddedView}.
*
* The location of the View Container within the containing View is specified by the Anchor
* `element`. Each View Container can have only one Anchor Element and each Anchor Element can only
* have a single View Container.
*
* Root elements of Views attached to this container become siblings of the Anchor Element in
* the Rendered View.
*
* To access a `ViewContainerRef` of an Element, you can either place a {@link Directive} injected
* with `ViewContainerRef` on the Element, or you obtain it via a {@link ViewChild} query.
* @stable
*/
export abstract class ViewContainerRef {
/**
* Anchor element that specifies the location of this container in the containing View.
* <!-- TODO: rename to anchorElement -->
*/
abstract get element(): ElementRef;
abstract get injector(): Injector;
abstract get parentInjector(): Injector;
/**
* Destroys all Views in this container.
*/
abstract clear(): void;
/**
* Returns the {@link ViewRef} for the View located in this container at the specified index.
*/
abstract get(index: number): ViewRef;
/**
* Returns the number of Views currently attached to this container.
*/
abstract get length(): number;
/**
* Instantiates an Embedded View based on the {@link TemplateRef `templateRef`} and inserts it
* into this container at the specified `index`.
*
* If `index` is not specified, the new View will be inserted as the last View in the container.
*
* Returns the {@link ViewRef} for the newly created View.
*/
abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number):
EmbeddedViewRef<C>;
/**
* Instantiates a single {@link Component} and inserts its Host View into this container at the
* specified `index`.
*
* The component is instantiated using its {@link ComponentFactory} which can be
* obtained via {@link ComponentFactoryResolver#resolveComponentFactory}.
*
* If `index` is not specified, the new View will be inserted as the last View in the container.
*
* You can optionally specify the {@link Injector} that will be used as parent for the Component.
*
* Returns the {@link ComponentRef} of the Host View created for the newly instantiated Component.
*/
abstract createComponent<C>(
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
projectableNodes?: any[][]): ComponentRef<C>;
/**
* Inserts a View identified by a {@link ViewRef} into the container at the specified `index`.
*
* If `index` is not specified, the new View will be inserted as the last View in the container.
*
* Returns the inserted {@link ViewRef}.
*/
abstract insert(viewRef: ViewRef, index?: number): ViewRef;
/**
* Moves a View identified by a {@link ViewRef} into the container at the specified `index`.
*
* Returns the inserted {@link ViewRef}.
*/
abstract move(viewRef: ViewRef, currentIndex: number): ViewRef;
/**
* Returns the index of the View, specified via {@link ViewRef}, within the current container or
* `-1` if this container doesn't contain the View.
*/
abstract indexOf(viewRef: ViewRef): number;
/**
* Destroys a View attached to this container at the specified `index`.
*
* If `index` is not specified, the last View in the container will be removed.
*/
abstract remove(index?: number): void;
/**
* Use along with {@link #insert} to move a View within the current container.
*
* If the `index` param is omitted, the last {@link ViewRef} is detached.
*/
abstract detach(index?: number): ViewRef;
}

View File

@ -0,0 +1,90 @@
/**
* @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 {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef} from '../change_detection/change_detector_ref';
/**
* @stable
*/
export abstract class ViewRef extends ChangeDetectorRef {
/**
* Destroys the view and all of the data structures associated with it.
*/
abstract destroy(): void;
abstract get destroyed(): boolean;
abstract onDestroy(callback: Function): any /** TODO #9100 */;
}
/**
* Represents an Angular View.
*
* <!-- TODO: move the next two paragraphs to the dev guide -->
* A View is a fundamental building block of the application UI. It is the smallest grouping of
* Elements which are created and destroyed together.
*
* Properties of elements in a View can change, but the structure (number and order) of elements in
* a View cannot. Changing the structure of Elements can only be done by inserting, moving or
* removing nested Views via a {@link ViewContainerRef}. Each View can contain many View Containers.
* <!-- /TODO -->
*
* ### Example
*
* Given this template...
*
* ```
* Count: {{items.length}}
* <ul>
* <li *ngFor="let item of items">{{item}}</li>
* </ul>
* ```
*
* We have two {@link TemplateRef}s:
*
* Outer {@link TemplateRef}:
* ```
* Count: {{items.length}}
* <ul>
* <ng-template ngFor let-item [ngForOf]="items"></ng-template>
* </ul>
* ```
*
* Inner {@link TemplateRef}:
* ```
* <li>{{item}}</li>
* ```
*
* Notice that the original template is broken down into two separate {@link TemplateRef}s.
*
* The outer/inner {@link TemplateRef}s are then assembled into views like so:
*
* ```
* <!-- ViewRef: outer-0 -->
* Count: 2
* <ul>
* <ng-template view-container-ref></ng-template>
* <!-- ViewRef: inner-1 --><li>first</li><!-- /ViewRef: inner-1 -->
* <!-- ViewRef: inner-2 --><li>second</li><!-- /ViewRef: inner-2 -->
* </ul>
* <!-- /ViewRef: outer-0 -->
* ```
* @experimental
*/
export abstract class EmbeddedViewRef<C> extends ViewRef {
abstract get context(): C;
abstract get rootNodes(): any[];
}
export interface InternalViewRef extends ViewRef {
detachFromAppRef(): void;
attachToAppRef(appRef: ApplicationRef): void;
}

View File

@ -0,0 +1,23 @@
/**
* @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
*/
/**
* This indirection is needed to free up Component, etc symbols in the public API
* to be used by the decorator versions of these annotations.
*/
import {Attribute, ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
import {ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module';
import {ViewEncapsulation} from './metadata/view';
export {ANALYZE_FOR_ENTRY_COMPONENTS, Attribute, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';
export {Component, ComponentDecorator, Directive, DirectiveDecorator, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './metadata/lifecycle_hooks';
export {CUSTOM_ELEMENTS_SCHEMA, ModuleWithProviders, NO_ERRORS_SCHEMA, NgModule, SchemaMetadata} from './metadata/ng_module';
export {ViewEncapsulation} from './metadata/view';

View File

@ -0,0 +1,415 @@
/**
* @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 {InjectionToken} from '../di/injection_token';
import {Type} from '../type';
import {makeParamDecorator, makePropDecorator} from '../util/decorators';
/**
* This token can be used to create a virtual provider that will populate the
* `entryComponents` fields of components and ng modules based on its `useValue`.
* All components that are referenced in the `useValue` value (either directly
* or in a nested array or map) will be added to the `entryComponents` property.
*
* ### Example
* The following example shows how the router can populate the `entryComponents`
* field of an NgModule based on the router configuration which refers
* to components.
*
* ```typescript
* // helper function inside the router
* function provideRoutes(routes) {
* return [
* {provide: ROUTES, useValue: routes},
* {provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: routes, multi: true}
* ];
* }
*
* // user code
* let routes = [
* {path: '/root', component: RootComp},
* {path: '/teams', component: TeamsComp}
* ];
*
* @NgModule({
* providers: [provideRoutes(routes)]
* })
* class ModuleWithRoutes {}
* ```
*
* @experimental
*/
export const ANALYZE_FOR_ENTRY_COMPONENTS = new InjectionToken<any>('AnalyzeForEntryComponents');
/**
* Type of the Attribute decorator / constructor function.
*
* @stable
*/
export interface AttributeDecorator {
/**
* Specifies that a constant attribute value should be injected.
*
* The directive can inject constant string literals of host element attributes.
*
* ### Example
*
* Suppose we have an `<input>` element and want to know its `type`.
*
* ```html
* <input type="text">
* ```
*
* A decorator can inject string literal `text` like so:
*
* {@example core/ts/metadata/metadata.ts region='attributeMetadata'}
*
* ### Example as TypeScript Decorator
*
* {@example core/ts/metadata/metadata.ts region='attributeFactory'}
*
* ### Example as ES5 DSL
*
* ```
* var MyComponent = ng
* .Component({...})
* .Class({
* constructor: [new ng.Attribute('title'), function(title) {
* ...
* }]
* })
* ```
*
* ### Example as ES5 annotation
*
* ```
* var MyComponent = function(title) {
* ...
* };
*
* MyComponent.annotations = [
* new ng.Component({...})
* ]
* MyComponent.parameters = [
* [new ng.Attribute('title')]
* ]
* ```
*
* @stable
*/
(name: string): any;
new (name: string): Attribute;
}
/**
* Type of the Attribute metadata.
*/
export interface Attribute { attributeName?: string; }
/**
* Attribute decorator and metadata.
*
* @stable
* @Annotation
*/
export const Attribute: AttributeDecorator =
makeParamDecorator('Attribute', [['attributeName', undefined]]);
/**
* Type of the Query metadata.
*
* @stable
*/
export interface Query {
descendants: boolean;
first: boolean;
read: any;
isViewQuery: boolean;
selector: any;
}
/**
* Base class for query metadata.
*
* See {@link ContentChildren}, {@link ContentChild}, {@link ViewChildren}, {@link ViewChild} for
* more information.
*
* @stable
*/
export abstract class Query {}
/**
* Type of the ContentChildren decorator / constructor function.
*
* See {@link ContentChildren}.
*
* @stable
*/
export interface ContentChildrenDecorator {
/**
* @whatItDoes Configures a content query.
*
* @howToUse
*
* {@example core/di/ts/contentChildren/content_children_howto.ts region='HowTo'}
*
* @description
*
* You can use ContentChildren to get the {@link QueryList} of elements or directives from the
* content DOM. Any time a child element is added, removed, or moved, the query list will be
* updated,
* and the changes observable of the query list will emit a new value.
*
* Content queries are set before the `ngAfterContentInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **descendants** - include only direct children or all descendants.
* * **read** - read a different token from the queried elements.
*
* Let's look at an example:
*
* {@example core/di/ts/contentChildren/content_children_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
*/
(selector: Type<any>|Function|string,
{descendants, read}?: {descendants?: boolean, read?: any}): any;
new (
selector: Type<any>|Function|string,
{descendants, read}?: {descendants?: boolean, read?: any}): Query;
}
/**
* Type of the ContentChildren metadata.
*
* @stable
* @Annotation
*/
export type ContentChildren = Query;
/**
* ContentChildren decorator and metadata.
*
* @stable
* @Annotation
*/
export const ContentChildren: ContentChildrenDecorator =
<ContentChildrenDecorator>makePropDecorator(
'ContentChildren',
[
['selector', undefined], {
first: false,
isViewQuery: false,
descendants: false,
read: undefined,
}
],
Query);
/**
* Type of the ContentChild decorator / constructor function.
*
*
* @stable
*/
export interface ContentChildDecorator {
/**
* @whatItDoes Configures a content query.
*
* @howToUse
*
* {@example core/di/ts/contentChild/content_child_howto.ts region='HowTo'}
*
* @description
*
* You can use ContentChild to get the first element or the directive matching the selector from
* the content DOM. If the content DOM changes, and a new child matches the selector,
* the property will be updated.
*
* Content queries are set before the `ngAfterContentInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried element.
*
* Let's look at an example:
*
* {@example core/di/ts/contentChild/content_child_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
*/
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ContentChild;
}
/**
* Type of the ContentChild metadata.
*
* See {@link ContentChild}.
*
* @stable
*/
export type ContentChild = Query;
/**
* ContentChild decorator and metadata.
*
* @stable
* @Annotation
*/
export const ContentChild: ContentChildDecorator = makePropDecorator(
'ContentChild',
[
['selector', undefined], {
first: true,
isViewQuery: false,
descendants: true,
read: undefined,
}
],
Query);
/**
* Type of the ViewChildren decorator / constructor function.
*
* See {@ViewChildren}.
*
* @stable
*/
export interface ViewChildrenDecorator {
/**
* @whatItDoes Configures a view query.
*
* @howToUse
*
* {@example core/di/ts/viewChildren/view_children_howto.ts region='HowTo'}
*
* @description
*
* You can use ViewChildren to get the {@link QueryList} of elements or directives from the
* view DOM. Any time a child element is added, removed, or moved, the query list will be updated,
* and the changes observable of the query list will emit a new value.
*
* View queries are set before the `ngAfterViewInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried elements.
*
* Let's look at an example:
*
* {@example core/di/ts/viewChildren/view_children_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
*/
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ViewChildren;
}
/**
* Type of the ViewChildren metadata.
*
* @stable
*/
export type ViewChildren = Query;
/**
* ViewChildren decorator and metadata.
*
* @stable
* @Annotation
*/
export const ViewChildren: ViewChildrenDecorator = makePropDecorator(
'ViewChildren',
[
['selector', undefined], {
first: false,
isViewQuery: true,
descendants: true,
read: undefined,
}
],
Query);
/**
* Type of the ViewChild decorator / constructor function.
*
* See {@link ViewChild}
*
* @stable
*/
export interface ViewChildDecorator {
/**
* @whatItDoes Configures a view query.
*
* @howToUse
*
* {@example core/di/ts/viewChild/view_child_howto.ts region='HowTo'}
*
* @description
*
* You can use ViewChild to get the first element or the directive matching the selector from the
* view DOM. If the view DOM changes, and a new child matches the selector,
* the property will be updated.
*
* View queries are set before the `ngAfterViewInit` callback is called.
*
* **Metadata Properties**:
*
* * **selector** - the directive type or the name used for querying.
* * **read** - read a different token from the queried elements.
*
* {@example core/di/ts/viewChild/view_child_example.ts region='Component'}
*
* **npm package**: `@angular/core`
*
* @stable
* @Annotation
*/
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string, {read}?: {read?: any}): ViewChild;
}
/**
* Type of the ViewChild metadata.
*
* @stable
*/
export type ViewChild = Query;
/**
* ViewChild decorator and metadata.
*
* @stable
* @Annotation
*/
export const ViewChild: ViewChildDecorator = makePropDecorator(
'ViewChild',
[
['selector', undefined], {
first: true,
isViewQuery: true,
descendants: true,
read: undefined,
}
],
Query);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
/**
* @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 {SimpleChange} from '../change_detection/change_detection_util';
/**
* @stable
*/
export enum LifecycleHooks {
OnInit,
OnDestroy,
DoCheck,
OnChanges,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked
}
/**
* A `changes` object whose keys are property names and
* values are instances of {@link SimpleChange}. See {@link OnChanges}
* @stable
*/
export interface SimpleChanges { [propName: string]: SimpleChange; }
export const LIFECYCLE_HOOKS_VALUES = [
LifecycleHooks.OnInit, LifecycleHooks.OnDestroy, LifecycleHooks.DoCheck, LifecycleHooks.OnChanges,
LifecycleHooks.AfterContentInit, LifecycleHooks.AfterContentChecked, LifecycleHooks.AfterViewInit,
LifecycleHooks.AfterViewChecked
];
/**
* @whatItDoes Lifecycle hook that is called when any data-bound property of a directive changes.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnChanges'}
*
* @description
* `ngOnChanges` is called right after the data-bound properties have been checked and before view
* and content children are checked if at least one of them has changed.
* The `changes` parameter contains the changed properties.
*
* See {@linkDocs guide/lifecycle-hooks#onchanges "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface OnChanges { ngOnChanges(changes: SimpleChanges): void; }
/**
* @whatItDoes Lifecycle hook that is called after data-bound properties of a directive are
* initialized.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnInit'}
*
* @description
* `ngOnInit` is called right after the directive's data-bound properties have been checked for the
* first time, and before any of its children have been checked. It is invoked only once when the
* directive is instantiated.
*
* See {@linkDocs guide/lifecycle-hooks "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface OnInit { ngOnInit(): void; }
/**
* @whatItDoes Lifecycle hook that is called when Angular dirty checks a directive.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='DoCheck'}
*
* @description
* `ngDoCheck` gets called to check the changes in the directives in addition to the default
* algorithm. The default change detection algorithm looks for differences by comparing
* bound-property values by reference across change detection runs.
*
* Note that a directive typically should not use both `DoCheck` and {@link OnChanges} to respond to
* changes on the same input, as `ngOnChanges` will continue to be called when the default change
* detector detects changes.
*
* See {@link KeyValueDiffers} and {@link IterableDiffers} for implementing custom dirty checking
* for collections.
*
* See {@linkDocs guide/lifecycle-hooks#docheck "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface DoCheck { ngDoCheck(): void; }
/**
* @whatItDoes Lifecycle hook that is called when a directive, pipe or service is destroyed.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnDestroy'}
*
* @description
* `ngOnDestroy` callback is typically used for any custom cleanup that needs to occur when the
* instance is destroyed.
*
* See {@linkDocs guide/lifecycle-hooks "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface OnDestroy { ngOnDestroy(): void; }
/**
*
* @whatItDoes Lifecycle hook that is called after a directive's content has been fully
* initialized.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterContentInit'}
*
* @description
* See {@linkDocs guide/lifecycle-hooks#aftercontent "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface AfterContentInit { ngAfterContentInit(): void; }
/**
* @whatItDoes Lifecycle hook that is called after every check of a directive's content.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterContentChecked'}
*
* @description
* See {@linkDocs guide/lifecycle-hooks#aftercontent "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface AfterContentChecked { ngAfterContentChecked(): void; }
/**
* @whatItDoes Lifecycle hook that is called after a component's view has been fully
* initialized.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterViewInit'}
*
* @description
* See {@linkDocs guide/lifecycle-hooks#afterview "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface AfterViewInit { ngAfterViewInit(): void; }
/**
* @whatItDoes Lifecycle hook that is called after every check of a component's view.
* @howToUse
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterViewChecked'}
*
* @description
* See {@linkDocs guide/lifecycle-hooks#afterview "Lifecycle Hooks Guide"}.
*
* @stable
*/
export interface AfterViewChecked { ngAfterViewChecked(): void; }

View File

@ -0,0 +1,202 @@
/**
* @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 {Provider} from '../di';
import {Type} from '../type';
import {TypeDecorator, makeDecorator} from '../util/decorators';
/**
* A wrapper around a module that also includes the providers.
*
* @stable
*/
export interface ModuleWithProviders {
ngModule: Type<any>;
providers?: Provider[];
}
/**
* Interface for schema definitions in @NgModules.
*
* @experimental
*/
export interface SchemaMetadata { name: string; }
/**
* Defines a schema that will allow:
* - any non-Angular elements with a `-` in their name,
* - any properties on elements with a `-` in their name which is the common rule for custom
* elements.
*
* @stable
*/
export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = {
name: 'custom-elements'
};
/**
* Defines a schema that will allow any property on any element.
*
* @experimental
*/
export const NO_ERRORS_SCHEMA: SchemaMetadata = {
name: 'no-errors-schema'
};
/**
* Type of the NgModule decorator / constructor function.
*
* @stable
*/
export interface NgModuleDecorator {
/**
* Defines an NgModule.
*/
(obj?: NgModule): TypeDecorator;
new (obj?: NgModule): NgModule;
}
/**
* Type of the NgModule metadata.
*
* @stable
*/
export interface NgModule {
/**
* Defines the set of injectable objects that are available in the injector
* of this module.
*
* ## Simple Example
*
* Here is an example of a class that can be injected:
*
* ```
* class Greeter {
* greet(name:string) {
* return 'Hello ' + name + '!';
* }
* }
*
* @NgModule({
* providers: [
* Greeter
* ]
* })
* class HelloWorld {
* greeter:Greeter;
*
* constructor(greeter:Greeter) {
* this.greeter = greeter;
* }
* }
* ```
*/
providers?: Provider[];
/**
* Specifies a list of directives/pipes that belong to this module.
*
* ### Example
*
* ```javascript
* @NgModule({
* declarations: [NgFor]
* })
* class CommonModule {
* }
* ```
*/
declarations?: Array<Type<any>|any[]>;
/**
* Specifies a list of modules whose exported directives/pipes
* should be available to templates in this module.
* This can also contain {@link ModuleWithProviders}.
*
* ### Example
*
* ```javascript
* @NgModule({
* imports: [CommonModule]
* })
* class MainModule {
* }
* ```
*/
imports?: Array<Type<any>|ModuleWithProviders|any[]>;
/**
* Specifies a list of directives/pipes/modules that can be used within the template
* of any component that is part of an Angular module
* that imports this Angular module.
*
* ### Example
*
* ```javascript
* @NgModule({
* exports: [NgFor]
* })
* class CommonModule {
* }
* ```
*/
exports?: Array<Type<any>|any[]>;
/**
* Specifies a list of components that should be compiled when this module is defined.
* For each component listed here, Angular will create a {@link ComponentFactory}
* and store it in the {@link ComponentFactoryResolver}.
*/
entryComponents?: Array<Type<any>|any[]>;
/**
* Defines the components that should be bootstrapped when
* this module is bootstrapped. The components listed here
* will automatically be added to `entryComponents`.
*/
bootstrap?: Array<Type<any>|any[]>;
/**
* Elements and properties that are not Angular components nor directives have to be declared in
* the schema.
*
* Available schemas:
* - `NO_ERRORS_SCHEMA`: any elements and properties are allowed,
* - `CUSTOM_ELEMENTS_SCHEMA`: any custom elements (tag name has "-") with any properties are
* allowed.
*
* @security When using one of `NO_ERRORS_SCHEMA` or `CUSTOM_ELEMENTS_SCHEMA` we're trusting that
* allowed elements (and its properties) securely escape inputs.
*/
schemas?: Array<SchemaMetadata|any[]>;
/**
* An opaque ID for this module, e.g. a name or a path. Used to identify modules in
* `getModuleFactory`. If left `undefined`, the `NgModule` will not be registered with
* `getModuleFactory`.
*/
id?: string;
}
/**
* NgModule decorator and metadata.
*
* @stable
* @Annotation
*/
export const NgModule: NgModuleDecorator = <NgModuleDecorator>makeDecorator('NgModule', {
providers: undefined,
declarations: undefined,
imports: undefined,
exports: undefined,
entryComponents: undefined,
bootstrap: undefined,
schemas: undefined,
id: undefined,
});

View File

@ -0,0 +1,97 @@
/**
* @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
*/
/**
* Defines template and style encapsulation options available for Component's {@link Component}.
*
* See {@link ViewMetadata#encapsulation}.
* @stable
*/
export enum ViewEncapsulation {
/**
* Emulate `Native` scoping of styles by adding an attribute containing surrogate id to the Host
* Element and pre-processing the style rules provided via
* {@link ViewMetadata#styles} or {@link ViewMetadata#stylesUrls}, and adding the new Host Element
* attribute to all selectors.
*
* This is the default option.
*/
Emulated,
/**
* Use the native encapsulation mechanism of the renderer.
*
* For the DOM this means using [Shadow DOM](https://w3c.github.io/webcomponents/spec/shadow/) and
* creating a ShadowRoot for Component's Host Element.
*/
Native,
/**
* Don't provide any template or style encapsulation.
*/
None
}
/**
* Metadata properties available for configuring Views.
*
* For details on the `@Component` annotation, see {@link Component}.
*
* ### Example
*
* ```
* @Component({
* selector: 'greet',
* template: 'Hello {{name}}!',
* })
* class Greet {
* name: string;
*
* constructor() {
* this.name = 'World';
* }
* }
* ```
*
* @deprecated Use Component instead.
*
* {@link Component}
*/
export class ViewMetadata {
/** {@link Component.templateUrl} */
templateUrl: string;
/** {@link Component.template} */
template: string;
/** {@link Component.stylesUrl} */
styleUrls: string[];
/** {@link Component.styles} */
styles: string[];
/** {@link Component.encapsulation} */
encapsulation: ViewEncapsulation;
/** {@link Component.animation} */
animations: any[];
/** {@link Component.interpolation} */
interpolation: [string, string];
constructor(
{templateUrl, template, encapsulation, styles, styleUrls, animations, interpolation}: {
templateUrl?: string,
template?: string,
encapsulation?: ViewEncapsulation,
styles?: string[],
styleUrls?: string[],
animations?: any[],
interpolation?: [string, string]
} = {}) {
this.templateUrl = templateUrl;
this.template = template;
this.styleUrls = styleUrls;
this.styles = styles;
this.encapsulation = encapsulation;
this.animations = animations;
this.interpolation = interpolation;
}
}

View File

@ -0,0 +1,37 @@
/**
* @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 {PlatformRef, PlatformRef_, createPlatformFactory} from './application_ref';
import {PLATFORM_ID} from './application_tokens';
import {Console} from './console';
import {Provider} from './di';
import {Reflector, reflector} from './reflection/reflection';
import {ReflectorReader} from './reflection/reflector_reader';
import {TestabilityRegistry} from './testability/testability';
function _reflector(): Reflector {
return reflector;
}
const _CORE_PLATFORM_PROVIDERS: Provider[] = [
// Set a default platform name for platforms that don't set it explicitly.
{provide: PLATFORM_ID, useValue: 'unknown'},
PlatformRef_,
{provide: PlatformRef, useExisting: PlatformRef_},
{provide: Reflector, useFactory: _reflector, deps: []},
{provide: ReflectorReader, useExisting: Reflector},
TestabilityRegistry,
Console,
];
/**
* This platform has to be included in any other platform
*
* @experimental
*/
export const platformCore = createPlatformFactory(null, 'core', _CORE_PLATFORM_PROVIDERS);

View File

@ -0,0 +1,90 @@
/**
* @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 {WtfScopeFn, createScope, detectWTF, endTimeRange, leave, startTimeRange} from './wtf_impl';
export {WtfScopeFn} from './wtf_impl';
/**
* True if WTF is enabled.
*/
export const wtfEnabled = detectWTF();
function noopScope(arg0?: any, arg1?: any): any {
return null;
}
/**
* Create trace scope.
*
* Scopes must be strictly nested and are analogous to stack frames, but
* do not have to follow the stack frames. Instead it is recommended that they follow logical
* nesting. You may want to use
* [Event
* Signatures](http://google.github.io/tracing-framework/instrumenting-code.html#custom-events)
* as they are defined in WTF.
*
* Used to mark scope entry. The return value is used to leave the scope.
*
* var myScope = wtfCreateScope('MyClass#myMethod(ascii someVal)');
*
* someMethod() {
* var s = myScope('Foo'); // 'Foo' gets stored in tracing UI
* // DO SOME WORK HERE
* return wtfLeave(s, 123); // Return value 123
* }
*
* Note, adding try-finally block around the work to ensure that `wtfLeave` gets called can
* negatively impact the performance of your application. For this reason we recommend that
* you don't add them to ensure that `wtfLeave` gets called. In production `wtfLeave` is a noop and
* so try-finally block has no value. When debugging perf issues, skipping `wtfLeave`, do to
* exception, will produce incorrect trace, but presence of exception signifies logic error which
* needs to be fixed before the app should be profiled. Add try-finally only when you expect that
* an exception is expected during normal execution while profiling.
*
* @experimental
*/
export const wtfCreateScope: (signature: string, flags?: any) => WtfScopeFn =
wtfEnabled ? createScope : (signature: string, flags?: any) => noopScope;
/**
* Used to mark end of Scope.
*
* - `scope` to end.
* - `returnValue` (optional) to be passed to the WTF.
*
* Returns the `returnValue for easy chaining.
* @experimental
*/
export const wtfLeave: <T>(scope: any, returnValue?: T) => T =
wtfEnabled ? leave : (s: any, r?: any) => r;
/**
* Used to mark Async start. Async are similar to scope but they don't have to be strictly nested.
* The return value is used in the call to [endAsync]. Async ranges only work if WTF has been
* enabled.
*
* someMethod() {
* var s = wtfStartTimeRange('HTTP:GET', 'some.url');
* var future = new Future.delay(5).then((_) {
* wtfEndTimeRange(s);
* });
* }
* @experimental
*/
export const wtfStartTimeRange: (rangeType: string, action: string) => any =
wtfEnabled ? startTimeRange : (rangeType: string, action: string) => null;
/**
* Ends a async time range operation.
* [range] is the return value from [wtfStartTimeRange] Async ranges only work if WTF has been
* enabled.
* @experimental
*/
export const wtfEndTimeRange: (range: any) => void = wtfEnabled ? endTimeRange : (r: any) => null;

View File

@ -0,0 +1,67 @@
/**
* @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 {global} from '../util';
/**
* A scope function for the Web Tracing Framework (WTF).
*
* @experimental
*/
export interface WtfScopeFn { (arg0?: any, arg1?: any): any; }
interface WTF {
trace: Trace;
}
interface Trace {
events: Events;
leaveScope(scope: Scope, returnValue: any): any /** TODO #9100 */;
beginTimeRange(rangeType: string, action: string): Range;
endTimeRange(range: Range): any /** TODO #9100 */;
}
export interface Range {}
interface Events {
createScope(signature: string, flags: any): Scope;
}
export interface Scope { (...args: any[] /** TODO #9100 */): any; }
let trace: Trace;
let events: Events;
export function detectWTF(): boolean {
const wtf: WTF = (global as any /** TODO #9100 */)['wtf'];
if (wtf) {
trace = wtf['trace'];
if (trace) {
events = trace['events'];
return true;
}
}
return false;
}
export function createScope(signature: string, flags: any = null): any {
return events.createScope(signature, flags);
}
export function leave<T>(scope: Scope, returnValue?: T): T {
trace.leaveScope(scope, returnValue);
return returnValue;
}
export function startTimeRange(rangeType: string, action: string): Range {
return trace.beginTimeRange(rangeType, action);
}
export function endTimeRange(range: Range): void {
trace.endTimeRange(range);
}

View File

@ -0,0 +1,25 @@
/**
* @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 {Type} from '../type';
import {GetterFn, MethodFn, SetterFn} from './types';
export interface PlatformReflectionCapabilities {
isReflectionEnabled(): boolean;
factory(type: Type<any>): Function;
hasLifecycleHook(type: any, lcProperty: string): boolean;
parameters(type: Type<any>): any[][];
annotations(type: Type<any>): any[];
propMetadata(typeOrFunc: Type<any>): {[key: string]: any[]};
getter(name: string): GetterFn;
setter(name: string): SetterFn;
method(name: string): MethodFn;
importUri(type: Type<any>): string;
resolveIdentifier(name: string, moduleUrl: string, members: string[], runtime: any): any;
resolveEnum(enumIdentifier: any, name: string): any;
}

View File

@ -0,0 +1,18 @@
/**
* @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 {ReflectionCapabilities} from './reflection_capabilities';
import {Reflector} from './reflector';
export {Reflector} from './reflector';
/**
* The {@link Reflector} used internally in Angular to access metadata
* about symbols.
*/
export const reflector = new Reflector(new ReflectionCapabilities());

View File

@ -0,0 +1,253 @@
/**
* @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 {Type, isType} from '../type';
import {global, stringify} from '../util';
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
import {GetterFn, MethodFn, SetterFn} from './types';
/**
* Attention: This regex has to hold even if the code is minified!
*/
export const DELEGATE_CTOR =
/^function\s+\S+\(\)\s*{\s*("use strict";)?\s*(return\s+)?(\S+\s+!==\s+null\s+&&\s+)?\S+\.apply\(this,\s*arguments\)/;
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
private _reflect: any;
constructor(reflect?: any) { this._reflect = reflect || global['Reflect']; }
isReflectionEnabled(): boolean { return true; }
factory<T>(t: Type<T>): (args: any[]) => T { return (...args: any[]) => new t(...args); }
/** @internal */
_zipTypesAndAnnotations(paramTypes: any[], paramAnnotations: any[]): any[][] {
let result: any[][];
if (typeof paramTypes === 'undefined') {
result = new Array(paramAnnotations.length);
} else {
result = new Array(paramTypes.length);
}
for (let i = 0; i < result.length; i++) {
// TS outputs Object for parameters without types, while Traceur omits
// the annotations. For now we preserve the Traceur behavior to aid
// migration, but this can be revisited.
if (typeof paramTypes === 'undefined') {
result[i] = [];
} else if (paramTypes[i] != Object) {
result[i] = [paramTypes[i]];
} else {
result[i] = [];
}
if (paramAnnotations && paramAnnotations[i] != null) {
result[i] = result[i].concat(paramAnnotations[i]);
}
}
return result;
}
private _ownParameters(type: Type<any>, parentCtor: any): any[][] {
// If we have no decorators, we only have function.length as metadata.
// In that case, to detect whether a child class declared an own constructor or not,
// we need to look inside of that constructor to check whether it is
// just calling the parent.
// This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
// that sets 'design:paramtypes' to []
// if a class inherits from another class but has no ctor declared itself.
if (DELEGATE_CTOR.exec(type.toString())) {
return null;
}
// Prefer the direct API.
if ((<any>type).parameters && (<any>type).parameters !== parentCtor.parameters) {
return (<any>type).parameters;
}
// API of tsickle for lowering decorators to properties on the class.
const tsickleCtorParams = (<any>type).ctorParameters;
if (tsickleCtorParams && tsickleCtorParams !== parentCtor.ctorParameters) {
// Newer tsickle uses a function closure
// Retain the non-function case for compatibility with older tsickle
const ctorParameters =
typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type);
const paramAnnotations = ctorParameters.map(
(ctorParam: any) =>
ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators));
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
// API for metadata created by invoking the decorators.
if (this._reflect != null && this._reflect.getOwnMetadata != null) {
const paramAnnotations = this._reflect.getOwnMetadata('parameters', type);
const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type);
if (paramTypes || paramAnnotations) {
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
}
// If a class has no decorators, at least create metadata
// based on function.length.
// Note: We know that this is a real constructor as we checked
// the content of the constructor above.
return new Array((<any>type.length)).fill(undefined);
}
parameters(type: Type<any>): any[][] {
// Note: only report metadata if we have at least one class decorator
// to stay in sync with the static reflector.
if (!isType(type)) {
return [];
}
const parentCtor = getParentCtor(type);
let parameters = this._ownParameters(type, parentCtor);
if (!parameters && parentCtor !== Object) {
parameters = this.parameters(parentCtor);
}
return parameters || [];
}
private _ownAnnotations(typeOrFunc: Type<any>, parentCtor: any): any[] {
// Prefer the direct API.
if ((<any>typeOrFunc).annotations && (<any>typeOrFunc).annotations !== parentCtor.annotations) {
let annotations = (<any>typeOrFunc).annotations;
if (typeof annotations === 'function' && annotations.annotations) {
annotations = annotations.annotations;
}
return annotations;
}
// API of tsickle for lowering decorators to properties on the class.
if ((<any>typeOrFunc).decorators && (<any>typeOrFunc).decorators !== parentCtor.decorators) {
return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators);
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('annotations', typeOrFunc);
}
}
annotations(typeOrFunc: Type<any>): any[] {
if (!isType(typeOrFunc)) {
return [];
}
const parentCtor = getParentCtor(typeOrFunc);
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
return parentAnnotations.concat(ownAnnotations);
}
private _ownPropMetadata(typeOrFunc: any, parentCtor: any): {[key: string]: any[]} {
// Prefer the direct API.
if ((<any>typeOrFunc).propMetadata &&
(<any>typeOrFunc).propMetadata !== parentCtor.propMetadata) {
let propMetadata = (<any>typeOrFunc).propMetadata;
if (typeof propMetadata === 'function' && propMetadata.propMetadata) {
propMetadata = propMetadata.propMetadata;
}
return propMetadata;
}
// API of tsickle for lowering decorators to properties on the class.
if ((<any>typeOrFunc).propDecorators &&
(<any>typeOrFunc).propDecorators !== parentCtor.propDecorators) {
const propDecorators = (<any>typeOrFunc).propDecorators;
const propMetadata = <{[key: string]: any[]}>{};
Object.keys(propDecorators).forEach(prop => {
propMetadata[prop] = convertTsickleDecoratorIntoMetadata(propDecorators[prop]);
});
return propMetadata;
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('propMetadata', typeOrFunc);
}
}
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
if (!isType(typeOrFunc)) {
return {};
}
const parentCtor = getParentCtor(typeOrFunc);
const propMetadata: {[key: string]: any[]} = {};
if (parentCtor !== Object) {
const parentPropMetadata = this.propMetadata(parentCtor);
Object.keys(parentPropMetadata).forEach((propName) => {
propMetadata[propName] = parentPropMetadata[propName];
});
}
const ownPropMetadata = this._ownPropMetadata(typeOrFunc, parentCtor);
if (ownPropMetadata) {
Object.keys(ownPropMetadata).forEach((propName) => {
const decorators: any[] = [];
if (propMetadata.hasOwnProperty(propName)) {
decorators.push(...propMetadata[propName]);
}
decorators.push(...ownPropMetadata[propName]);
propMetadata[propName] = decorators;
});
}
return propMetadata;
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
return type instanceof Type && lcProperty in type.prototype;
}
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }
setter(name: string): SetterFn {
return <SetterFn>new Function('o', 'v', 'return o.' + name + ' = v;');
}
method(name: string): MethodFn {
const functionBody = `if (!o.${name}) throw new Error('"${name}" is undefined');
return o.${name}.apply(o, args);`;
return <MethodFn>new Function('o', 'args', functionBody);
}
// There is not a concept of import uri in Js, but this is useful in developing Dart applications.
importUri(type: any): string {
// StaticSymbol
if (typeof type === 'object' && type['filePath']) {
return type['filePath'];
}
// Runtime type
return `./${stringify(type)}`;
}
resolveIdentifier(name: string, moduleUrl: string, members: string[], runtime: any): any {
return runtime;
}
resolveEnum(enumIdentifier: any, name: string): any { return enumIdentifier[name]; }
}
function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[] {
if (!decoratorInvocations) {
return [];
}
return decoratorInvocations.map(decoratorInvocation => {
const decoratorType = decoratorInvocation.type;
const annotationCls = decoratorType.annotationCls;
const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : [];
return new annotationCls(...annotationArgs);
});
}
function getParentCtor(ctor: Function): Type<any> {
const parentProto = Object.getPrototypeOf(ctor.prototype);
const parentCtor = parentProto ? parentProto.constructor : null;
// Note: We always use `Object` as the null value
// to simplify checking later on.
return parentCtor || Object;
}

View File

@ -0,0 +1,59 @@
/**
* @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 {Type} from '../type';
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
import {ReflectorReader} from './reflector_reader';
import {GetterFn, MethodFn, SetterFn} from './types';
export {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
export {GetterFn, MethodFn, SetterFn} from './types';
/**
* Provides access to reflection data about symbols. Used internally by Angular
* to power dependency injection and compilation.
*/
export class Reflector extends ReflectorReader {
constructor(public reflectionCapabilities: PlatformReflectionCapabilities) { super(); }
updateCapabilities(caps: PlatformReflectionCapabilities) { this.reflectionCapabilities = caps; }
factory(type: Type<any>): Function { return this.reflectionCapabilities.factory(type); }
parameters(typeOrFunc: Type<any>): any[][] {
return this.reflectionCapabilities.parameters(typeOrFunc);
}
annotations(typeOrFunc: Type<any>): any[] {
return this.reflectionCapabilities.annotations(typeOrFunc);
}
propMetadata(typeOrFunc: Type<any>): {[key: string]: any[]} {
return this.reflectionCapabilities.propMetadata(typeOrFunc);
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty);
}
getter(name: string): GetterFn { return this.reflectionCapabilities.getter(name); }
setter(name: string): SetterFn { return this.reflectionCapabilities.setter(name); }
method(name: string): MethodFn { return this.reflectionCapabilities.method(name); }
importUri(type: any): string { return this.reflectionCapabilities.importUri(type); }
resolveIdentifier(name: string, moduleUrl: string, members: string[], runtime: any): any {
return this.reflectionCapabilities.resolveIdentifier(name, moduleUrl, members, runtime);
}
resolveEnum(identifier: any, name: string): any {
return this.reflectionCapabilities.resolveEnum(identifier, name);
}
}

View File

@ -0,0 +1,20 @@
/**
* @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
*/
/**
* Provides read-only access to reflection data about symbols. Used internally by Angular
* to power dependency injection and compilation.
*/
export abstract class ReflectorReader {
abstract parameters(typeOrFunc: /*Type*/ any): any[][];
abstract annotations(typeOrFunc: /*Type*/ any): any[];
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
abstract importUri(typeOrFunc: /*Type*/ any): string;
abstract resolveIdentifier(name: string, moduleUrl: string, members: string[], runtime: any): any;
abstract resolveEnum(identifier: any, name: string): any;
}

View File

@ -0,0 +1,11 @@
/**
* @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
*/
export type SetterFn = (obj: any, value: any) => void;
export type GetterFn = (obj: any) => any;
export type MethodFn = (obj: any, args: any[]) => any;

View File

@ -0,0 +1,10 @@
/**
* @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
*/
// Public API for render
export {RenderComponentType, Renderer, Renderer2, RendererFactory2, RendererType2, RootRenderer} from './render/api';

View File

@ -0,0 +1,180 @@
/**
* @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 {InjectionToken, Injector} from '../di';
import {ViewEncapsulation} from '../metadata/view';
/**
* @deprecated Use `RendererType2` (and `Renderer2`) instead.
*/
export class RenderComponentType {
constructor(
public id: string, public templateUrl: string, public slotCount: number,
public encapsulation: ViewEncapsulation, public styles: Array<string|any[]>,
public animations: any) {}
}
/**
* @deprecated Debug info is handeled internally in the view engine now.
*/
export abstract class RenderDebugInfo {
abstract get injector(): Injector;
abstract get component(): any;
abstract get providerTokens(): any[];
abstract get references(): {[key: string]: any};
abstract get context(): any;
abstract get source(): string;
}
/**
* @deprecated Use the `Renderer2` instead.
*/
export interface DirectRenderer {
remove(node: any): void;
appendChild(node: any, parent: any): void;
insertBefore(node: any, refNode: any): void;
nextSibling(node: any): any;
parentElement(node: any): any;
}
/**
* @deprecated Use the `Renderer2` instead.
*/
export abstract class Renderer {
abstract selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any;
abstract createElement(parentElement: any, name: string, debugInfo?: RenderDebugInfo): any;
abstract createViewRoot(hostElement: any): any;
abstract createTemplateAnchor(parentElement: any, debugInfo?: RenderDebugInfo): any;
abstract createText(parentElement: any, value: string, debugInfo?: RenderDebugInfo): any;
abstract projectNodes(parentElement: any, nodes: any[]): void;
abstract attachViewAfter(node: any, viewRootNodes: any[]): void;
abstract detachView(viewRootNodes: any[]): void;
abstract destroyView(hostElement: any, viewAllNodes: any[]): void;
abstract listen(renderElement: any, name: string, callback: Function): Function;
abstract listenGlobal(target: string, name: string, callback: Function): Function;
abstract setElementProperty(renderElement: any, propertyName: string, propertyValue: any): void;
abstract setElementAttribute(renderElement: any, attributeName: string, attributeValue: string):
void;
/**
* Used only in debug mode to serialize property changes to dom nodes as attributes.
*/
abstract setBindingDebugInfo(renderElement: any, propertyName: string, propertyValue: string):
void;
abstract setElementClass(renderElement: any, className: string, isAdd: boolean): void;
abstract setElementStyle(renderElement: any, styleName: string, styleValue: string): void;
abstract invokeElementMethod(renderElement: any, methodName: string, args?: any[]): void;
abstract setText(renderNode: any, text: string): void;
abstract animate(
element: any, startingStyles: any, keyframes: any[], duration: number, delay: number,
easing: string, previousPlayers?: any[]): any;
}
export const Renderer2Interceptor = new InjectionToken<Renderer2[]>('Renderer2Interceptor');
/**
* Injectable service that provides a low-level interface for modifying the UI.
*
* Use this service to bypass Angular's templating and make custom UI changes that can't be
* expressed declaratively. For example if you need to set a property or an attribute whose name is
* not statically known, use {@link #setElementProperty} or {@link #setElementAttribute}
* respectively.
*
* If you are implementing a custom renderer, you must implement this interface.
*
* The default Renderer implementation is `DomRenderer`. Also available is `WebWorkerRenderer`.
*
* @deprecated Use `RendererFactory2` instead.
*/
export abstract class RootRenderer {
abstract renderComponent(componentType: RenderComponentType): Renderer;
}
/**
* @experimental
*/
export interface RendererType2 {
id: string;
encapsulation: ViewEncapsulation;
styles: (string|any[])[];
data: {[kind: string]: any};
}
/**
* @experimental
*/
export abstract class RendererFactory2 {
abstract createRenderer(hostElement: any, type: RendererType2): Renderer2;
}
/**
* @experimental
*/
export abstract class Renderer2 {
/**
* This field can be used to store arbitrary data on this renderer instance.
* This is useful for renderers that delegate to other renderers.
*/
abstract get data(): {[key: string]: any};
abstract destroy(): void;
abstract createElement(name: string, namespace?: string): any;
abstract createComment(value: string): any;
abstract createText(value: string): any;
/**
* This property is allowed to be null / undefined,
* in which case the view engine won't call it.
* This is used as a performance optimization for production mode.
*/
destroyNode: (node: any) => void | null;
abstract appendChild(parent: any, newChild: any): void;
abstract insertBefore(parent: any, newChild: any, refChild: any): void;
abstract removeChild(parent: any, oldChild: any): void;
abstract selectRootElement(selectorOrNode: string|any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract parentNode(node: any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract nextSibling(node: any): any;
abstract setAttribute(el: any, name: string, value: string, namespace?: string): void;
abstract removeAttribute(el: any, name: string, namespace?: string): void;
abstract addClass(el: any, name: string): void;
abstract removeClass(el: any, name: string): void;
abstract setStyle(
el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean): void;
abstract removeStyle(el: any, style: string, hasVendorPrefix: boolean): void;
abstract setProperty(el: any, name: string, value: any): void;
abstract setValue(node: any, value: string): void;
abstract listen(
target: 'window'|'document'|'body'|any, eventName: string,
callback: (event: any) => boolean | void): () => void;
}

View File

@ -0,0 +1,34 @@
/**
* @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
*/
/**
* A SecurityContext marks a location that has dangerous security implications, e.g. a DOM property
* like `innerHTML` that could cause Cross Site Scripting (XSS) security bugs when improperly
* handled.
*
* See DomSanitizer for more details on security in Angular applications.
*
* @stable
*/
export enum SecurityContext {
NONE,
HTML,
STYLE,
SCRIPT,
URL,
RESOURCE_URL,
}
/**
* Sanitizer is used by the views to sanitize potentially dangerous values.
*
* @stable
*/
export abstract class Sanitizer {
abstract sanitize(context: SecurityContext, value: string): string;
}

View File

@ -0,0 +1,179 @@
/**
* @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 {Injectable} from '../di';
import {scheduleMicroTask} from '../util';
import {NgZone} from '../zone/ng_zone';
/**
* Testability API.
* `declare` keyword causes tsickle to generate externs, so these methods are
* not renamed by Closure Compiler.
* @experimental
*/
export declare interface PublicTestability {
isStable(): boolean;
whenStable(callback: Function): void;
findProviders(using: any, provider: string, exactMatch: boolean): any[];
}
/**
* The Testability service provides testing hooks that can be accessed from
* the browser and by services such as Protractor. Each bootstrapped Angular
* application on the page will have an instance of Testability.
* @experimental
*/
@Injectable()
export class Testability implements PublicTestability {
/** @internal */
_pendingCount: number = 0;
/** @internal */
_isZoneStable: boolean = true;
/**
* Whether any work was done since the last 'whenStable' callback. This is
* useful to detect if this could have potentially destabilized another
* component while it is stabilizing.
* @internal
*/
_didWork: boolean = false;
/** @internal */
_callbacks: Function[] = [];
constructor(private _ngZone: NgZone) { this._watchAngularEvents(); }
/** @internal */
_watchAngularEvents(): void {
this._ngZone.onUnstable.subscribe({
next: () => {
this._didWork = true;
this._isZoneStable = false;
}
});
this._ngZone.runOutsideAngular(() => {
this._ngZone.onStable.subscribe({
next: () => {
NgZone.assertNotInAngularZone();
scheduleMicroTask(() => {
this._isZoneStable = true;
this._runCallbacksIfReady();
});
}
});
});
}
increasePendingRequestCount(): number {
this._pendingCount += 1;
this._didWork = true;
return this._pendingCount;
}
decreasePendingRequestCount(): number {
this._pendingCount -= 1;
if (this._pendingCount < 0) {
throw new Error('pending async requests below zero');
}
this._runCallbacksIfReady();
return this._pendingCount;
}
isStable(): boolean {
return this._isZoneStable && this._pendingCount == 0 && !this._ngZone.hasPendingMacrotasks;
}
/** @internal */
_runCallbacksIfReady(): void {
if (this.isStable()) {
// Schedules the call backs in a new frame so that it is always async.
scheduleMicroTask(() => {
while (this._callbacks.length !== 0) {
(this._callbacks.pop())(this._didWork);
}
this._didWork = false;
});
} else {
// Not Ready
this._didWork = true;
}
}
whenStable(callback: Function): void {
this._callbacks.push(callback);
this._runCallbacksIfReady();
}
getPendingRequestCount(): number { return this._pendingCount; }
/** @deprecated use findProviders */
findBindings(using: any, provider: string, exactMatch: boolean): any[] {
// TODO(juliemr): implement.
return [];
}
findProviders(using: any, provider: string, exactMatch: boolean): any[] {
// TODO(juliemr): implement.
return [];
}
}
/**
* A global registry of {@link Testability} instances for specific elements.
* @experimental
*/
@Injectable()
export class TestabilityRegistry {
/** @internal */
_applications = new Map<any, Testability>();
constructor() { _testabilityGetter.addToWindow(this); }
registerApplication(token: any, testability: Testability) {
this._applications.set(token, testability);
}
getTestability(elem: any): Testability { return this._applications.get(elem); }
getAllTestabilities(): Testability[] { return Array.from(this._applications.values()); }
getAllRootElements(): any[] { return Array.from(this._applications.keys()); }
findTestabilityInTree(elem: Node, findInAncestors: boolean = true): Testability {
return _testabilityGetter.findTestabilityInTree(this, elem, findInAncestors);
}
}
/**
* Adapter interface for retrieving the `Testability` service associated for a
* particular context.
*
* @experimental Testability apis are primarily intended to be used by e2e test tool vendors like
* the Protractor team.
*/
export interface GetTestability {
addToWindow(registry: TestabilityRegistry): void;
findTestabilityInTree(registry: TestabilityRegistry, elem: any, findInAncestors: boolean):
Testability;
}
class _NoopGetTestability implements GetTestability {
addToWindow(registry: TestabilityRegistry): void {}
findTestabilityInTree(registry: TestabilityRegistry, elem: any, findInAncestors: boolean):
Testability {
return null;
}
}
/**
* Set the {@link GetTestability} implementation used by the Angular testing framework.
* @experimental
*/
export function setTestabilityGetter(getter: GetTestability): void {
_testabilityGetter = getter;
}
let _testabilityGetter: GetTestability = new _NoopGetTestability();

25
packages/core/src/type.ts Normal file
View File

@ -0,0 +1,25 @@
/**
* @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
*/
/**
* @whatItDoes Represents a type that a Component or other object is instances of.
*
* @description
*
* An example of a `Type` is `MyCustomComponent` class, which in JavaScript is be represented by
* the `MyCustomComponent` constructor function.
*
* @stable
*/
export const Type = Function;
export function isType(v: any): v is Type<any> {
return typeof v === 'function';
}
export interface Type<T> extends Function { new (...args: any[]): T; }

74
packages/core/src/util.ts Normal file
View File

@ -0,0 +1,74 @@
/**
* @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
*/
// TODO(jteplitz602): Load WorkerGlobalScope from lib.webworker.d.ts file #3492
declare var WorkerGlobalScope: any /** TODO #9100 */;
// CommonJS / Node have global context exposed as "global" variable.
// We don't want to include the whole node.d.ts this this compilation unit so we'll just fake
// the global "global" var for now.
declare var global: any /** TODO #9100 */;
const __window = typeof window !== 'undefined' && window;
const __self = typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' &&
self instanceof WorkerGlobalScope && self;
const __global = typeof global !== 'undefined' && global;
const _global: {[name: string]: any} = __window || __global || __self;
export {_global as global};
// When Symbol.iterator doesn't exist, retrieves the key used in es6-shim
declare const Symbol: any;
let _symbolIterator: any = null;
export function getSymbolIterator(): string|symbol {
if (!_symbolIterator) {
const Symbol = _global['Symbol'];
if (Symbol && Symbol.iterator) {
_symbolIterator = Symbol.iterator;
} else {
// es6-shim specific logic
const keys = Object.getOwnPropertyNames(Map.prototype);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (key !== 'entries' && key !== 'size' &&
(Map as any).prototype[key] === Map.prototype['entries']) {
_symbolIterator = key;
}
}
}
}
return _symbolIterator;
}
export function scheduleMicroTask(fn: Function) {
Zone.current.scheduleMicroTask('scheduleMicrotask', fn);
}
// JS has NaN !== NaN
export function looseIdentical(a: any, b: any): boolean {
return a === b || typeof a === 'number' && typeof b === 'number' && isNaN(a) && isNaN(b);
}
export function stringify(token: any): string {
if (typeof token === 'string') {
return token;
}
if (token == null) {
return '' + token;
}
if (token.overriddenName) {
return `${token.overriddenName}`;
}
if (token.name) {
return `${token.name}`;
}
const res = token.toString();
const newLineIndex = res.indexOf('\n');
return newLineIndex === -1 ? res : res.substring(0, newLineIndex);
}

View File

@ -0,0 +1,385 @@
/**
* @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 {Type} from '../type';
import {global, stringify} from '../util';
let _nextClassId = 0;
const Reflect = global['Reflect'];
/**
* Declares the interface to be used with {@link Class}.
*
* @stable
*/
export type ClassDefinition = {
/**
* Optional argument for specifying the superclass.
*/
extends?: Type<any>;
/**
* Required constructor function for a class.
*
* The function may be optionally wrapped in an `Array`, in which case additional parameter
* annotations may be specified.
* The number of arguments and the number of parameter annotations must match.
*
* See {@link Class} for example of usage.
*/
constructor: Function | any[];
} &
{
/**
* Other methods on the class. Note that values should have type 'Function' but TS requires
* all properties to have a narrower type than the index signature.
*/
[x: string]: Type<any>|Function|any[];
};
/**
* An interface implemented by all Angular type decorators, which allows them to be used as ES7
* decorators as well as
* Angular DSL syntax.
*
* DSL syntax:
*
* ```
* var MyClass = ng
* .Component({...})
* .Class({...});
* ```
*
* ES7 syntax:
*
* ```
* @ng.Component({...})
* class MyClass {...}
* ```
* @stable
*/
export interface TypeDecorator {
/**
* Invoke as ES7 decorator.
*/
<T extends Type<any>>(type: T): T;
// Make TypeDecorator assignable to built-in ParameterDecorator type.
// ParameterDecorator is declared in lib.d.ts as a `declare type`
// so we cannot declare this interface as a subtype.
// see https://github.com/angular/angular/issues/3379#issuecomment-126169417
(target: Object, propertyKey?: string|symbol, parameterIndex?: number): void;
/**
* Storage for the accumulated annotations so far used by the DSL syntax.
*
* Used by {@link Class} to annotate the generated class.
*/
annotations: any[];
/**
* Generate a class from the definition and annotate it with {@link TypeDecorator#annotations}.
*/
Class(obj: ClassDefinition): Type<any>;
}
function extractAnnotation(annotation: any): any {
if (typeof annotation === 'function' && annotation.hasOwnProperty('annotation')) {
// it is a decorator, extract annotation
annotation = annotation.annotation;
}
return annotation;
}
function applyParams(fnOrArray: (Function | any[]), key: string): Function {
if (fnOrArray === Object || fnOrArray === String || fnOrArray === Function ||
fnOrArray === Number || fnOrArray === Array) {
throw new Error(`Can not use native ${stringify(fnOrArray)} as constructor`);
}
if (typeof fnOrArray === 'function') {
return fnOrArray;
}
if (Array.isArray(fnOrArray)) {
const annotations: any[] = fnOrArray;
const annoLength = annotations.length - 1;
const fn: Function = fnOrArray[annoLength];
if (typeof fn !== 'function') {
throw new Error(
`Last position of Class method array must be Function in key ${key} was '${stringify(fn)}'`);
}
if (annoLength != fn.length) {
throw new Error(
`Number of annotations (${annoLength}) does not match number of arguments (${fn.length}) in the function: ${stringify(fn)}`);
}
const paramsAnnotations: any[][] = [];
for (let i = 0, ii = annotations.length - 1; i < ii; i++) {
const paramAnnotations: any[] = [];
paramsAnnotations.push(paramAnnotations);
const annotation = annotations[i];
if (Array.isArray(annotation)) {
for (let j = 0; j < annotation.length; j++) {
paramAnnotations.push(extractAnnotation(annotation[j]));
}
} else if (typeof annotation === 'function') {
paramAnnotations.push(extractAnnotation(annotation));
} else {
paramAnnotations.push(annotation);
}
}
Reflect.defineMetadata('parameters', paramsAnnotations, fn);
return fn;
}
throw new Error(
`Only Function or Array is supported in Class definition for key '${key}' is '${stringify(fnOrArray)}'`);
}
/**
* Provides a way for expressing ES6 classes with parameter annotations in ES5.
*
* ## Basic Example
*
* ```
* var Greeter = ng.Class({
* constructor: function(name) {
* this.name = name;
* },
*
* greet: function() {
* alert('Hello ' + this.name + '!');
* }
* });
* ```
*
* is equivalent to ES6:
*
* ```
* class Greeter {
* constructor(name) {
* this.name = name;
* }
*
* greet() {
* alert('Hello ' + this.name + '!');
* }
* }
* ```
*
* or equivalent to ES5:
*
* ```
* var Greeter = function (name) {
* this.name = name;
* }
*
* Greeter.prototype.greet = function () {
* alert('Hello ' + this.name + '!');
* }
* ```
*
* ### Example with parameter annotations
*
* ```
* var MyService = ng.Class({
* constructor: [String, [new Optional(), Service], function(name, myService) {
* ...
* }]
* });
* ```
*
* is equivalent to ES6:
*
* ```
* class MyService {
* constructor(name: string, @Optional() myService: Service) {
* ...
* }
* }
* ```
*
* ### Example with inheritance
*
* ```
* var Shape = ng.Class({
* constructor: (color) {
* this.color = color;
* }
* });
*
* var Square = ng.Class({
* extends: Shape,
* constructor: function(color, size) {
* Shape.call(this, color);
* this.size = size;
* }
* });
* ```
* @suppress {globalThis}
* @stable
*/
export function Class(clsDef: ClassDefinition): Type<any> {
const constructor = applyParams(
clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor');
let proto = constructor.prototype;
if (clsDef.hasOwnProperty('extends')) {
if (typeof clsDef.extends === 'function') {
(<Function>constructor).prototype = proto =
Object.create((<Function>clsDef.extends).prototype);
} else {
throw new Error(
`Class definition 'extends' property must be a constructor function was: ${stringify(clsDef.extends)}`);
}
}
for (const key in clsDef) {
if (key !== 'extends' && key !== 'prototype' && clsDef.hasOwnProperty(key)) {
proto[key] = applyParams(clsDef[key], key);
}
}
if (this && this.annotations instanceof Array) {
Reflect.defineMetadata('annotations', this.annotations, constructor);
}
const constructorName = constructor['name'];
if (!constructorName || constructorName === 'constructor') {
(constructor as any)['overriddenName'] = `class${_nextClassId++}`;
}
return <Type<any>>constructor;
}
/**
* @suppress {globalThis}
*/
export function makeDecorator(
name: string, props: {[name: string]: any}, parentClass?: any,
chainFn: (fn: Function) => void = null): (...args: any[]) => (cls: any) => any {
const metaCtor = makeMetadataCtor([props]);
function DecoratorFactory(objOrType: any): (cls: any) => any {
if (!(Reflect && Reflect.getOwnMetadata)) {
throw 'reflect-metadata shim is required when using class decorators';
}
if (this instanceof DecoratorFactory) {
metaCtor.call(this, objOrType);
return this;
}
const annotationInstance = new (<any>DecoratorFactory)(objOrType);
const chainAnnotation =
typeof this === 'function' && Array.isArray(this.annotations) ? this.annotations : [];
chainAnnotation.push(annotationInstance);
const TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls: Type<any>) {
const annotations = Reflect.getOwnMetadata('annotations', cls) || [];
annotations.push(annotationInstance);
Reflect.defineMetadata('annotations', annotations, cls);
return cls;
};
TypeDecorator.annotations = chainAnnotation;
TypeDecorator.Class = Class;
if (chainFn) chainFn(TypeDecorator);
return TypeDecorator;
}
if (parentClass) {
DecoratorFactory.prototype = Object.create(parentClass.prototype);
}
DecoratorFactory.prototype.toString = () => `@${name}`;
(<any>DecoratorFactory).annotationCls = DecoratorFactory;
return DecoratorFactory;
}
function makeMetadataCtor(props: ([string, any] | {[key: string]: any})[]): any {
return function ctor(...args: any[]) {
props.forEach((prop, i) => {
const argVal = args[i];
if (Array.isArray(prop)) {
// plain parameter
this[prop[0]] = argVal === undefined ? prop[1] : argVal;
} else {
for (const propName in prop) {
this[propName] =
argVal && argVal.hasOwnProperty(propName) ? argVal[propName] : prop[propName];
}
}
});
};
}
export function makeParamDecorator(
name: string, props: ([string, any] | {[name: string]: any})[], parentClass?: any): any {
const metaCtor = makeMetadataCtor(props);
function ParamDecoratorFactory(...args: any[]): any {
if (this instanceof ParamDecoratorFactory) {
metaCtor.apply(this, args);
return this;
}
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
(<any>ParamDecorator).annotation = annotationInstance;
return ParamDecorator;
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
const parameters: any[][] = Reflect.getOwnMetadata('parameters', cls) || [];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
while (parameters.length <= index) {
parameters.push(null);
}
parameters[index] = parameters[index] || [];
parameters[index].push(annotationInstance);
Reflect.defineMetadata('parameters', parameters, cls);
return cls;
}
}
if (parentClass) {
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
ParamDecoratorFactory.prototype.toString = () => `@${name}`;
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
return ParamDecoratorFactory;
}
export function makePropDecorator(
name: string, props: ([string, any] | {[key: string]: any})[], parentClass?: any): any {
const metaCtor = makeMetadataCtor(props);
function PropDecoratorFactory(...args: any[]): any {
if (this instanceof PropDecoratorFactory) {
metaCtor.apply(this, args);
return this;
}
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
return function PropDecorator(target: any, name: string) {
const meta = Reflect.getOwnMetadata('propMetadata', target.constructor) || {};
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
meta[name].unshift(decoratorInstance);
Reflect.defineMetadata('propMetadata', meta, target.constructor);
};
}
if (parentClass) {
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
PropDecoratorFactory.prototype.toString = () => `@${name}`;
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
return PropDecoratorFactory;
}

View File

@ -0,0 +1,41 @@
/**
* @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 {Observable} from 'rxjs/Observable';
import {$$observable as symbolObservable} from 'rxjs/symbol/observable';
/**
* Determine if the argument is shaped like a Promise
*/
export function isPromise(obj: any): obj is Promise<any> {
// allow any Promise/A+ compliant thenable.
// It's up to the caller to ensure that obj.then conforms to the spec
return !!obj && typeof obj.then === 'function';
}
/**
* Determine if the argument is an Observable
*/
export function isObservable(obj: any | Observable<any>): obj is Observable<any> {
return !!(obj && obj[symbolObservable]);
}
// TODO(misko): replace with Object.assign once we require ES6.
export function merge<V>(m1: {[key: string]: V}, m2: {[key: string]: V}): {[key: string]: V} {
const m: {[key: string]: V} = {};
for (const k of Object.keys(m1)) {
m[k] = m1[k];
}
for (const k of Object.keys(m2)) {
m[k] = m2[k];
}
return m;
}

View File

@ -0,0 +1,27 @@
/**
* @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
*/
/**
* @whatItDoes Represents the version of Angular
*
* @stable
*/
export class Version {
constructor(public full: string) {}
get major(): string { return this.full.split('.')[0]; }
get minor(): string { return this.full.split('.')[1]; }
get patch(): string { return this.full.split('.').slice(2).join('.'); }
}
/**
* @stable
*/
export const VERSION = new Version('0.0.0-PLACEHOLDER');

View File

@ -0,0 +1,313 @@
/**
* @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 {isDevMode} from '../application_ref';
import {Renderer2, RendererType2} from '../render/api';
import {SecurityContext} from '../security';
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementHandleEventFn, NodeData, NodeDef, NodeFlags, OutputDef, OutputType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData, asProviderData} from './types';
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, filterQueryId, getParentRenderElement, resolveViewDefinition, sliceErrorStack, splitMatchedQueriesDsl, splitNamespace} from './util';
const NOOP: any = () => {};
export function anchorDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
ngContentIndex: number, childCount: number, handleEvent?: ElementHandleEventFn,
templateFactory?: ViewDefinitionFactory): NodeDef {
if (!handleEvent) {
handleEvent = NOOP;
}
flags |= NodeFlags.TypeElement;
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
// skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const template = templateFactory ? resolveViewDefinition(templateFactory) : null;
return {
// will bet set by the view definition
index: undefined,
parent: undefined,
renderParent: undefined,
bindingIndex: undefined,
outputIndex: undefined,
// regular values
flags,
childFlags: 0,
directChildFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: [],
outputs: [],
element: {
ns: undefined,
name: undefined,
attrs: undefined, template, source,
componentProvider: undefined,
componentView: undefined,
componentRendererType: undefined,
publicProviders: undefined,
allProviders: undefined, handleEvent
},
provider: undefined,
text: undefined,
query: undefined,
ngContent: undefined
};
}
export function elementDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
ngContentIndex: number, childCount: number, namespaceAndName: string,
fixedAttrs: [string, string][] = [],
bindings?:
([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] |
[
BindingType.ElementAttribute | BindingType.ElementProperty |
BindingType.ComponentHostProperty,
string, SecurityContext
])[],
outputs?: ([string, string])[], handleEvent?: ElementHandleEventFn,
componentView?: () => ViewDefinition, componentRendererType?: RendererType2): NodeDef {
if (!handleEvent) {
handleEvent = NOOP;
}
// skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
let ns: string;
let name: string;
if (namespaceAndName) {
[ns, name] = splitNamespace(namespaceAndName);
}
bindings = bindings || [];
const bindingDefs: BindingDef[] = new Array(bindings.length);
for (let i = 0; i < bindings.length; i++) {
const entry = bindings[i];
let bindingDef: BindingDef;
const bindingType = entry[0];
const [ns, name] = splitNamespace(entry[1]);
let securityContext: SecurityContext;
let suffix: string;
switch (bindingType) {
case BindingType.ElementStyle:
suffix = <string>entry[2];
break;
case BindingType.ElementAttribute:
case BindingType.ElementProperty:
case BindingType.ComponentHostProperty:
securityContext = <SecurityContext>entry[2];
break;
}
bindingDefs[i] = {type: bindingType, ns, name, nonMinifiedName: name, securityContext, suffix};
}
outputs = outputs || [];
const outputDefs: OutputDef[] = new Array(outputs.length);
for (let i = 0; i < outputs.length; i++) {
const [target, eventName] = outputs[i];
outputDefs[i] = {
type: OutputType.ElementOutput,
target: <any>target, eventName,
propName: undefined
};
}
fixedAttrs = fixedAttrs || [];
const attrs = <[string, string, string][]>fixedAttrs.map(([namespaceAndName, value]) => {
const [ns, name] = splitNamespace(namespaceAndName);
return [ns, name, value];
});
// This is needed as the jit compiler always uses an empty hash as default RendererType2,
// which is not filled for host views.
if (componentRendererType && componentRendererType.encapsulation == null) {
componentRendererType = null;
}
if (componentView) {
flags |= NodeFlags.ComponentView;
}
flags |= NodeFlags.TypeElement;
return {
// will bet set by the view definition
index: undefined,
parent: undefined,
renderParent: undefined,
bindingIndex: undefined,
outputIndex: undefined,
// regular values
flags,
childFlags: 0,
directChildFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount,
bindings: bindingDefs,
outputs: outputDefs,
element: {
ns,
name,
attrs,
source,
template: undefined,
// will bet set by the view definition
componentProvider: undefined, componentView, componentRendererType,
publicProviders: undefined,
allProviders: undefined, handleEvent,
},
provider: undefined,
text: undefined,
query: undefined,
ngContent: undefined
};
}
export function createElement(view: ViewData, renderHost: any, def: NodeDef): ElementData {
const elDef = def.element;
const rootSelectorOrNode = view.root.selectorOrNode;
const renderer = view.renderer;
let el: any;
if (view.parent || !rootSelectorOrNode) {
if (elDef.name) {
el = renderer.createElement(elDef.name, elDef.ns);
} else {
el = renderer.createComment('');
}
const parentEl = getParentRenderElement(view, renderHost, def);
if (parentEl) {
renderer.appendChild(parentEl, el);
}
} else {
el = renderer.selectRootElement(rootSelectorOrNode);
}
if (elDef.attrs) {
for (let i = 0; i < elDef.attrs.length; i++) {
const [ns, name, value] = elDef.attrs[i];
renderer.setAttribute(el, name, value, ns);
}
}
return el;
}
export function listenToElementOutputs(view: ViewData, compView: ViewData, def: NodeDef, el: any) {
for (let i = 0; i < def.outputs.length; i++) {
const output = def.outputs[i];
const handleEventClosure = renderEventHandlerClosure(
view, def.index, elementEventFullName(output.target, output.eventName));
let listenTarget = output.target;
let listenerView = view;
if (output.target === 'component') {
listenTarget = null;
listenerView = compView;
}
const disposable =
<any>listenerView.renderer.listen(listenTarget || el, output.eventName, handleEventClosure);
view.disposables[def.outputIndex + i] = disposable;
}
}
function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
return (event: any) => dispatchEvent(view, index, eventName, event);
}
export function checkAndUpdateElementInline(
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any): boolean {
const bindLen = def.bindings.length;
let changed = false;
if (bindLen > 0 && checkAndUpdateElementValue(view, def, 0, v0)) changed = true;
if (bindLen > 1 && checkAndUpdateElementValue(view, def, 1, v1)) changed = true;
if (bindLen > 2 && checkAndUpdateElementValue(view, def, 2, v2)) changed = true;
if (bindLen > 3 && checkAndUpdateElementValue(view, def, 3, v3)) changed = true;
if (bindLen > 4 && checkAndUpdateElementValue(view, def, 4, v4)) changed = true;
if (bindLen > 5 && checkAndUpdateElementValue(view, def, 5, v5)) changed = true;
if (bindLen > 6 && checkAndUpdateElementValue(view, def, 6, v6)) changed = true;
if (bindLen > 7 && checkAndUpdateElementValue(view, def, 7, v7)) changed = true;
if (bindLen > 8 && checkAndUpdateElementValue(view, def, 8, v8)) changed = true;
if (bindLen > 9 && checkAndUpdateElementValue(view, def, 9, v9)) changed = true;
return changed;
}
export function checkAndUpdateElementDynamic(view: ViewData, def: NodeDef, values: any[]): boolean {
let changed = false;
for (let i = 0; i < values.length; i++) {
if (checkAndUpdateElementValue(view, def, i, values[i])) changed = true;
}
return changed;
}
function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
if (!checkAndUpdateBinding(view, def, bindingIdx, value)) {
return false;
}
const binding = def.bindings[bindingIdx];
const elData = asElementData(view, def.index);
const renderNode = elData.renderElement;
const name = binding.name;
switch (binding.type) {
case BindingType.ElementAttribute:
setElementAttribute(view, binding, renderNode, binding.ns, name, value);
break;
case BindingType.ElementClass:
setElementClass(view, renderNode, name, value);
break;
case BindingType.ElementStyle:
setElementStyle(view, binding, renderNode, name, value);
break;
case BindingType.ElementProperty:
setElementProperty(view, binding, renderNode, name, value);
break;
case BindingType.ComponentHostProperty:
setElementProperty(elData.componentView, binding, renderNode, name, value);
break;
}
return true;
}
function setElementAttribute(
view: ViewData, binding: BindingDef, renderNode: any, ns: string, name: string, value: any) {
const securityContext = binding.securityContext;
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
renderValue = renderValue != null ? renderValue.toString() : null;
const renderer = view.renderer;
if (value != null) {
renderer.setAttribute(renderNode, name, renderValue, ns);
} else {
renderer.removeAttribute(renderNode, name, ns);
}
}
function setElementClass(view: ViewData, renderNode: any, name: string, value: boolean) {
const renderer = view.renderer;
if (value) {
renderer.addClass(renderNode, name);
} else {
renderer.removeClass(renderNode, name);
}
}
function setElementStyle(
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
let renderValue = view.root.sanitizer.sanitize(SecurityContext.STYLE, value);
if (renderValue != null) {
renderValue = renderValue.toString();
const unit = binding.suffix;
if (unit != null) {
renderValue = renderValue + unit;
}
} else {
renderValue = null;
}
const renderer = view.renderer;
if (renderValue != null) {
renderer.setStyle(renderNode, name, renderValue, false, false);
} else {
renderer.removeStyle(renderNode, name, false);
}
}
function setElementProperty(
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
const securityContext = binding.securityContext;
let renderValue = securityContext ? view.root.sanitizer.sanitize(securityContext, value) : value;
view.renderer.setProperty(renderNode, name, renderValue);
}

View File

@ -0,0 +1,43 @@
/**
* @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 {ERROR_DEBUG_CONTEXT, ERROR_ORIGINAL_ERROR, getDebugContext} from '../errors';
import {DebugContext, ViewState} from './types';
export function expressionChangedAfterItHasBeenCheckedError(
context: DebugContext, oldValue: any, currValue: any, isFirstCheck: boolean): Error {
let msg =
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`;
if (isFirstCheck) {
msg +=
` It seems like the view has been created after its parent and its children have been dirty checked.` +
` Has it been created in a change detection hook ?`;
}
return viewDebugError(msg, context);
}
export function viewWrappedDebugError(originalError: any, context: DebugContext): Error {
const err = viewDebugError(originalError.message, context);
(err as any)[ERROR_ORIGINAL_ERROR] = originalError;
return err;
}
export function viewDebugError(msg: string, context: DebugContext): Error {
const err = new Error(msg);
(err as any)[ERROR_DEBUG_CONTEXT] = context;
err.stack = context.source;
return err;
}
export function isViewDebugError(err: Error): boolean {
return !!getDebugContext(err);
}
export function viewDestroyedError(action: string): Error {
return new Error(`ViewDestroyedError: Attempt to use a destroyed view: ${action}`);
}

View File

@ -0,0 +1,21 @@
/**
* @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
*/
export {anchorDef, elementDef} from './element';
export {ngContentDef} from './ng_content';
export {directiveDef, pipeDef, providerDef} from './provider';
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
export {queryDef} from './query';
export {ViewRef_, createComponentFactory, getComponentViewDefinitionFactory, nodeValue} from './refs';
export {initServicesIfNeeded} from './services';
export {textDef} from './text';
export {EMPTY_ARRAY, EMPTY_MAP, createRendererType2, elementEventFullName, inlineInterpolate, interpolate, rootRenderNodes, unwrapValue} from './util';
export {viewDef} from './view';
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
export * from './types';

View File

@ -0,0 +1,48 @@
/**
* @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 {NodeDef, NodeFlags, ViewData, asElementData} from './types';
import {RenderNodeAction, getParentRenderElement, visitProjectedRenderNodes} from './util';
export function ngContentDef(ngContentIndex: number, index: number): NodeDef {
return {
// will bet set by the view definition
index: undefined,
parent: undefined,
renderParent: undefined,
bindingIndex: undefined,
outputIndex: undefined,
// regular values
flags: NodeFlags.TypeNgContent,
childFlags: 0,
directChildFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {}, ngContentIndex,
childCount: 0,
bindings: [],
outputs: [],
element: undefined,
provider: undefined,
text: undefined,
query: undefined,
ngContent: {index}
};
}
export function appendNgContent(view: ViewData, renderHost: any, def: NodeDef) {
const parentEl = getParentRenderElement(view, renderHost, def);
if (!parentEl) {
// Nothing to do if there is no parent element.
return;
}
const ngContentIndex = def.ngContent.index;
visitProjectedRenderNodes(
view, ngContentIndex, RenderNodeAction.AppendChild, parentEl, undefined, undefined);
}

View File

@ -0,0 +1,487 @@
/**
* @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, SimpleChange, SimpleChanges} from '../change_detection/change_detection';
import {Injector} from '../di';
import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {ViewEncapsulation} from '../metadata/view';
import {Renderer as RendererV1, Renderer2, RendererFactory2, RendererType2} from '../render/api';
import {createChangeDetectorRef, createInjector, createRendererV1, createTemplateRef, createViewContainerRef} from './refs';
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types';
import {checkBinding, dispatchEvent, filterQueryId, isComponentView, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
const RendererV1TokenKey = tokenKey(RendererV1);
const Renderer2TokenKey = tokenKey(Renderer2);
const ElementRefTokenKey = tokenKey(ElementRef);
const ViewContainerRefTokenKey = tokenKey(ViewContainerRef);
const TemplateRefTokenKey = tokenKey(TemplateRef);
const ChangeDetectorRefTokenKey = tokenKey(ChangeDetectorRef);
const InjectorRefTokenKey = tokenKey(Injector);
const NOT_CREATED = new Object();
export function directiveDef(
flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], childCount: number,
ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
outputs?: {[name: string]: string}): NodeDef {
const bindings: BindingDef[] = [];
if (props) {
for (let prop in props) {
const [bindingIndex, nonMinifiedName] = props[prop];
bindings[bindingIndex] = {
type: BindingType.DirectiveProperty,
name: prop, nonMinifiedName,
ns: undefined,
securityContext: undefined,
suffix: undefined
};
}
}
const outputDefs: OutputDef[] = [];
if (outputs) {
for (let propName in outputs) {
outputDefs.push(
{type: OutputType.DirectiveOutput, propName, target: null, eventName: outputs[propName]});
}
}
flags |= NodeFlags.TypeDirective;
return _def(flags, matchedQueries, childCount, ctor, ctor, deps, bindings, outputDefs);
}
export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef {
flags |= NodeFlags.TypePipe;
return _def(flags, null, 0, ctor, ctor, deps);
}
export function providerDef(
flags: NodeFlags, matchedQueries: [string | number, QueryValueType][], token: any, value: any,
deps: ([DepFlags, any] | any)[]): NodeDef {
return _def(flags, matchedQueries, 0, token, value, deps);
}
export function _def(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][], childCount: number,
token: any, value: any, deps: ([DepFlags, any] | any)[], bindings?: BindingDef[],
outputs?: OutputDef[]): NodeDef {
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
if (!outputs) {
outputs = [];
}
if (!bindings) {
bindings = [];
}
const depDefs: DepDef[] = deps.map(value => {
let token: any;
let flags: DepFlags;
if (Array.isArray(value)) {
[flags, token] = value;
} else {
flags = DepFlags.None;
token = value;
}
return {flags, token, tokenKey: tokenKey(token)};
});
return {
// will bet set by the view definition
index: undefined,
parent: undefined,
renderParent: undefined,
bindingIndex: undefined,
outputIndex: undefined,
// regular values
flags,
childFlags: 0,
directChildFlags: 0,
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references,
ngContentIndex: undefined, childCount, bindings, outputs,
element: undefined,
provider: {token, tokenKey: tokenKey(token), value, deps: depDefs},
text: undefined,
query: undefined,
ngContent: undefined
};
}
export function createProviderInstance(view: ViewData, def: NodeDef): any {
return def.flags & NodeFlags.LazyProvider ? NOT_CREATED : _createProviderInstance(view, def);
}
export function createPipeInstance(view: ViewData, def: NodeDef): any {
// deps are looked up from component.
let compView = view;
while (compView.parent && !isComponentView(compView)) {
compView = compView.parent;
}
// pipes can see the private services of the component
const allowPrivateServices = true;
// pipes are always eager and classes!
return createClass(
compView.parent, viewParentEl(compView), allowPrivateServices, def.provider.value,
def.provider.deps);
}
export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
// components can see other private services, other directives can't.
const allowPrivateServices = (def.flags & NodeFlags.Component) > 0;
const providerDef = def.provider;
// directives are always eager and classes!
const instance =
createClass(view, def.parent, allowPrivateServices, def.provider.value, def.provider.deps);
if (def.outputs.length) {
for (let i = 0; i < def.outputs.length; i++) {
const output = def.outputs[i];
const subscription = instance[output.propName].subscribe(
eventHandlerClosure(view, def.parent.index, output.eventName));
view.disposables[def.outputIndex + i] = subscription.unsubscribe.bind(subscription);
}
}
return instance;
}
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
return (event: any) => dispatchEvent(view, index, eventName, event);
}
export function checkAndUpdateDirectiveInline(
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any): boolean {
const providerData = asProviderData(view, def.index);
const directive = providerData.instance;
let changed = false;
let changes: SimpleChanges;
const bindLen = def.bindings.length;
if (bindLen > 0 && checkBinding(view, def, 0, v0)) {
changed = true;
changes = updateProp(view, providerData, def, 0, v0, changes);
};
if (bindLen > 1 && checkBinding(view, def, 1, v1)) {
changed = true;
changes = updateProp(view, providerData, def, 1, v1, changes);
};
if (bindLen > 2 && checkBinding(view, def, 2, v2)) {
changed = true;
changes = updateProp(view, providerData, def, 2, v2, changes);
};
if (bindLen > 3 && checkBinding(view, def, 3, v3)) {
changed = true;
changes = updateProp(view, providerData, def, 3, v3, changes);
};
if (bindLen > 4 && checkBinding(view, def, 4, v4)) {
changed = true;
changes = updateProp(view, providerData, def, 4, v4, changes);
};
if (bindLen > 5 && checkBinding(view, def, 5, v5)) {
changed = true;
changes = updateProp(view, providerData, def, 5, v5, changes);
};
if (bindLen > 6 && checkBinding(view, def, 6, v6)) {
changed = true;
changes = updateProp(view, providerData, def, 6, v6, changes);
};
if (bindLen > 7 && checkBinding(view, def, 7, v7)) {
changed = true;
changes = updateProp(view, providerData, def, 7, v7, changes);
};
if (bindLen > 8 && checkBinding(view, def, 8, v8)) {
changed = true;
changes = updateProp(view, providerData, def, 8, v8, changes);
};
if (bindLen > 9 && checkBinding(view, def, 9, v9)) {
changed = true;
changes = updateProp(view, providerData, def, 9, v9, changes);
};
if (changes) {
directive.ngOnChanges(changes);
}
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
directive.ngOnInit();
}
if (def.flags & NodeFlags.DoCheck) {
directive.ngDoCheck();
}
return changed;
}
export function checkAndUpdateDirectiveDynamic(
view: ViewData, def: NodeDef, values: any[]): boolean {
const providerData = asProviderData(view, def.index);
const directive = providerData.instance;
let changed = false;
let changes: SimpleChanges;
for (let i = 0; i < values.length; i++) {
if (checkBinding(view, def, i, values[i])) {
changed = true;
changes = updateProp(view, providerData, def, i, values[i], changes);
}
}
if (changes) {
directive.ngOnChanges(changes);
}
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
directive.ngOnInit();
}
if (def.flags & NodeFlags.DoCheck) {
directive.ngDoCheck();
}
return changed;
}
function _createProviderInstance(view: ViewData, def: NodeDef): any {
// private services can see other private services
const allowPrivateServices = (def.flags & NodeFlags.PrivateProvider) > 0;
const providerDef = def.provider;
let injectable: any;
switch (def.flags & NodeFlags.Types) {
case NodeFlags.TypeClassProvider:
injectable =
createClass(view, def.parent, allowPrivateServices, providerDef.value, providerDef.deps);
break;
case NodeFlags.TypeFactoryProvider:
injectable =
callFactory(view, def.parent, allowPrivateServices, providerDef.value, providerDef.deps);
break;
case NodeFlags.TypeUseExistingProvider:
injectable = resolveDep(view, def.parent, allowPrivateServices, providerDef.deps[0]);
break;
case NodeFlags.TypeValueProvider:
injectable = providerDef.value;
break;
}
return injectable;
}
function createClass(
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, ctor: any, deps: DepDef[]): any {
const len = deps.length;
let injectable: any;
switch (len) {
case 0:
injectable = new ctor();
break;
case 1:
injectable = new ctor(resolveDep(view, elDef, allowPrivateServices, deps[0]));
break;
case 2:
injectable = new ctor(
resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, elDef, allowPrivateServices, deps[1]));
break;
case 3:
injectable = new ctor(
resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, elDef, allowPrivateServices, deps[1]),
resolveDep(view, elDef, allowPrivateServices, deps[2]));
break;
default:
const depValues = new Array(len);
for (let i = 0; i < len; i++) {
depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]);
}
injectable = new ctor(...depValues);
}
return injectable;
}
function callFactory(
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, factory: any,
deps: DepDef[]): any {
const len = deps.length;
let injectable: any;
switch (len) {
case 0:
injectable = factory();
break;
case 1:
injectable = factory(resolveDep(view, elDef, allowPrivateServices, deps[0]));
break;
case 2:
injectable = factory(
resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, elDef, allowPrivateServices, deps[1]));
break;
case 3:
injectable = factory(
resolveDep(view, elDef, allowPrivateServices, deps[0]),
resolveDep(view, elDef, allowPrivateServices, deps[1]),
resolveDep(view, elDef, allowPrivateServices, deps[2]));
break;
default:
const depValues = Array(len);
for (let i = 0; i < len; i++) {
depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]);
}
injectable = factory(...depValues);
}
return injectable;
}
export function resolveDep(
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
notFoundValue = Injector.THROW_IF_NOT_FOUND): any {
if (depDef.flags & DepFlags.Value) {
return depDef.token;
}
const startView = view;
if (depDef.flags & DepFlags.Optional) {
notFoundValue = null;
}
const tokenKey = depDef.tokenKey;
if (depDef.flags & DepFlags.SkipSelf) {
allowPrivateServices = false;
elDef = elDef.parent;
}
while (view) {
if (elDef) {
switch (tokenKey) {
case RendererV1TokenKey: {
const compView = findCompView(view, elDef, allowPrivateServices);
return createRendererV1(compView);
}
case Renderer2TokenKey: {
const compView = findCompView(view, elDef, allowPrivateServices);
return compView.renderer;
}
case ElementRefTokenKey:
return new ElementRef(asElementData(view, elDef.index).renderElement);
case ViewContainerRefTokenKey:
return createViewContainerRef(view, elDef);
case TemplateRefTokenKey: {
if (elDef.element.template) {
return createTemplateRef(view, elDef);
}
break;
}
case ChangeDetectorRefTokenKey: {
let cdView = findCompView(view, elDef, allowPrivateServices);
return createChangeDetectorRef(cdView);
}
case InjectorRefTokenKey:
return createInjector(view, elDef);
default:
const providerDef =
(allowPrivateServices ? elDef.element.allProviders :
elDef.element.publicProviders)[tokenKey];
if (providerDef) {
const providerData = asProviderData(view, providerDef.index);
if (providerData.instance === NOT_CREATED) {
providerData.instance = _createProviderInstance(view, providerDef);
}
return providerData.instance;
}
}
}
allowPrivateServices = isComponentView(view);
elDef = viewParentEl(view);
view = view.parent;
}
return startView.root.injector.get(depDef.token, notFoundValue);
}
function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) {
let compView: ViewData;
if (allowPrivateServices) {
compView = asElementData(view, elDef.index).componentView;
} else {
compView = view;
while (compView.parent && !isComponentView(compView)) {
compView = compView.parent;
}
}
return compView;
}
function updateProp(
view: ViewData, providerData: ProviderData, def: NodeDef, bindingIdx: number, value: any,
changes: SimpleChanges): SimpleChanges {
if (def.flags & NodeFlags.Component) {
const compView = asElementData(view, def.parent.index).componentView;
if (compView.def.flags & ViewFlags.OnPush) {
compView.state |= ViewState.ChecksEnabled;
}
}
const binding = def.bindings[bindingIdx];
const propName = binding.name;
// Note: This is still safe with Closure Compiler as
// the user passed in the property name as an object has to `providerDef`,
// so Closure Compiler will have renamed the property correctly already.
providerData.instance[propName] = value;
if (def.flags & NodeFlags.OnChanges) {
changes = changes || {};
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
const binding = def.bindings[bindingIdx];
changes[binding.nonMinifiedName] =
new SimpleChange(oldValue, value, (view.state & ViewState.FirstCheck) !== 0);
}
view.oldValues[def.bindingIndex + bindingIdx] = value;
return changes;
}
export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: NodeFlags) {
if (!(view.def.nodeFlags & lifecycles)) {
return;
}
const nodes = view.def.nodes;
for (let i = 0; i < nodes.length; i++) {
const nodeDef = nodes[i];
let parent = nodeDef.parent;
if (!parent && nodeDef.flags & lifecycles) {
// matching root node (e.g. a pipe)
callProviderLifecycles(view, i, nodeDef.flags & lifecycles);
}
if ((nodeDef.childFlags & lifecycles) === 0) {
// no child matches one of the lifecycles
i += nodeDef.childCount;
}
while (parent && (parent.flags & NodeFlags.TypeElement) &&
i === parent.index + parent.childCount) {
// last child of an element
if (parent.directChildFlags & lifecycles) {
callElementProvidersLifecycles(view, parent, lifecycles);
}
parent = parent.parent;
}
}
}
function callElementProvidersLifecycles(view: ViewData, elDef: NodeDef, lifecycles: NodeFlags) {
for (let i = elDef.index + 1; i <= elDef.index + elDef.childCount; i++) {
const nodeDef = view.def.nodes[i];
if (nodeDef.flags & lifecycles) {
callProviderLifecycles(view, i, nodeDef.flags & lifecycles);
}
// only visit direct children
i += nodeDef.childCount;
}
}
function callProviderLifecycles(view: ViewData, index: number, lifecycles: NodeFlags) {
const provider = asProviderData(view, index).instance;
Services.setCurrentNode(view, index);
if (lifecycles & NodeFlags.AfterContentInit) {
provider.ngAfterContentInit();
}
if (lifecycles & NodeFlags.AfterContentChecked) {
provider.ngAfterContentChecked();
}
if (lifecycles & NodeFlags.AfterViewInit) {
provider.ngAfterViewInit();
}
if (lifecycles & NodeFlags.AfterViewChecked) {
provider.ngAfterViewChecked();
}
if (lifecycles & NodeFlags.OnDestroy) {
provider.ngOnDestroy();
}
}

View File

@ -0,0 +1,189 @@
/**
* @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 {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeFlags, ProviderData, PureExpressionData, Services, ViewData, asPureExpressionData} from './types';
import {checkAndUpdateBinding, tokenKey} from './util';
export function purePipeDef(argCount: number): NodeDef {
// argCount + 1 to include the pipe as first arg
return _pureExpressionDef(NodeFlags.TypePurePipe, new Array(argCount + 1));
}
export function pureArrayDef(argCount: number): NodeDef {
return _pureExpressionDef(NodeFlags.TypePureArray, new Array(argCount));
}
export function pureObjectDef(propertyNames: string[]): NodeDef {
return _pureExpressionDef(NodeFlags.TypePureObject, propertyNames);
}
function _pureExpressionDef(flags: NodeFlags, propertyNames: string[]): NodeDef {
const bindings: BindingDef[] = new Array(propertyNames.length);
for (let i = 0; i < propertyNames.length; i++) {
const prop = propertyNames[i];
bindings[i] = {
type: BindingType.PureExpressionProperty,
name: prop,
ns: undefined,
nonMinifiedName: prop,
securityContext: undefined,
suffix: undefined
};
}
return {
// will bet set by the view definition
index: undefined,
parent: undefined,
renderParent: undefined,
bindingIndex: undefined,
outputIndex: undefined,
// regular values
flags,
childFlags: 0,
directChildFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {},
ngContentIndex: undefined,
childCount: 0, bindings,
outputs: [],
element: undefined,
provider: undefined,
text: undefined,
query: undefined,
ngContent: undefined
};
}
export function createPureExpression(view: ViewData, def: NodeDef): PureExpressionData {
return {value: undefined};
}
export function checkAndUpdatePureExpressionInline(
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any): boolean {
const bindings = def.bindings;
let changed = false;
const bindLen = bindings.length;
if (bindLen > 0 && checkAndUpdateBinding(view, def, 0, v0)) changed = true;
if (bindLen > 1 && checkAndUpdateBinding(view, def, 1, v1)) changed = true;
if (bindLen > 2 && checkAndUpdateBinding(view, def, 2, v2)) changed = true;
if (bindLen > 3 && checkAndUpdateBinding(view, def, 3, v3)) changed = true;
if (bindLen > 4 && checkAndUpdateBinding(view, def, 4, v4)) changed = true;
if (bindLen > 5 && checkAndUpdateBinding(view, def, 5, v5)) changed = true;
if (bindLen > 6 && checkAndUpdateBinding(view, def, 6, v6)) changed = true;
if (bindLen > 7 && checkAndUpdateBinding(view, def, 7, v7)) changed = true;
if (bindLen > 8 && checkAndUpdateBinding(view, def, 8, v8)) changed = true;
if (bindLen > 9 && checkAndUpdateBinding(view, def, 9, v9)) changed = true;
if (changed) {
const data = asPureExpressionData(view, def.index);
let value: any;
switch (def.flags & NodeFlags.Types) {
case NodeFlags.TypePureArray:
value = new Array(bindings.length);
if (bindLen > 0) value[0] = v0;
if (bindLen > 1) value[1] = v1;
if (bindLen > 2) value[2] = v2;
if (bindLen > 3) value[3] = v3;
if (bindLen > 4) value[4] = v4;
if (bindLen > 5) value[5] = v5;
if (bindLen > 6) value[6] = v6;
if (bindLen > 7) value[7] = v7;
if (bindLen > 8) value[8] = v8;
if (bindLen > 9) value[9] = v9;
break;
case NodeFlags.TypePureObject:
value = {};
if (bindLen > 0) value[bindings[0].name] = v0;
if (bindLen > 1) value[bindings[1].name] = v1;
if (bindLen > 2) value[bindings[2].name] = v2;
if (bindLen > 3) value[bindings[3].name] = v3;
if (bindLen > 4) value[bindings[4].name] = v4;
if (bindLen > 5) value[bindings[5].name] = v5;
if (bindLen > 6) value[bindings[6].name] = v6;
if (bindLen > 7) value[bindings[7].name] = v7;
if (bindLen > 8) value[bindings[8].name] = v8;
if (bindLen > 9) value[bindings[9].name] = v9;
break;
case NodeFlags.TypePurePipe:
const pipe = v0;
switch (bindLen) {
case 1:
value = pipe.transform(v0);
break;
case 2:
value = pipe.transform(v1);
break;
case 3:
value = pipe.transform(v1, v2);
break;
case 4:
value = pipe.transform(v1, v2, v3);
break;
case 5:
value = pipe.transform(v1, v2, v3, v4);
break;
case 6:
value = pipe.transform(v1, v2, v3, v4, v5);
break;
case 7:
value = pipe.transform(v1, v2, v3, v4, v5, v6);
break;
case 8:
value = pipe.transform(v1, v2, v3, v4, v5, v6, v7);
break;
case 9:
value = pipe.transform(v1, v2, v3, v4, v5, v6, v7, v8);
break;
case 10:
value = pipe.transform(v1, v2, v3, v4, v5, v6, v7, v8, v9);
break;
}
break;
}
data.value = value;
}
return changed;
}
export function checkAndUpdatePureExpressionDynamic(
view: ViewData, def: NodeDef, values: any[]): boolean {
const bindings = def.bindings;
let changed = false;
for (let i = 0; i < values.length; i++) {
// Note: We need to loop over all values, so that
// the old values are updates as well!
if (checkAndUpdateBinding(view, def, i, values[i])) {
changed = true;
}
}
if (changed) {
const data = asPureExpressionData(view, def.index);
let value: any;
switch (def.flags & NodeFlags.Types) {
case NodeFlags.TypePureArray:
value = values;
break;
case NodeFlags.TypePureObject:
value = {};
for (let i = 0; i < values.length; i++) {
value[bindings[i].name] = values[i];
}
break;
case NodeFlags.TypePurePipe:
const pipe = values[0];
const params = values.slice(1);
value = (<any>pipe.transform)(...params);
break;
}
data.value = value;
}
return changed;
}

View File

@ -0,0 +1,194 @@
/**
* @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 {ElementRef} from '../linker/element_ref';
import {QueryList} from '../linker/query_list';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {createTemplateRef, createViewContainerRef} from './refs';
import {NodeDef, NodeFlags, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types';
import {declaredViewContainer, filterQueryId, isEmbeddedView, viewParentEl} from './util';
export function queryDef(
flags: NodeFlags, id: number, bindings: {[propName: string]: QueryBindingType}): NodeDef {
let bindingDefs: QueryBindingDef[] = [];
for (let propName in bindings) {
const bindingType = bindings[propName];
bindingDefs.push({propName, bindingType});
}
return {
// will bet set by the view definition
index: undefined,
parent: undefined,
renderParent: undefined,
bindingIndex: undefined,
outputIndex: undefined,
// regular values
flags,
childFlags: 0,
directChildFlags: 0,
childMatchedQueries: 0,
ngContentIndex: undefined,
matchedQueries: {},
matchedQueryIds: 0,
references: {},
childCount: 0,
bindings: [],
outputs: [],
element: undefined,
provider: undefined,
text: undefined,
query: {id, filterId: filterQueryId(id), bindings: bindingDefs},
ngContent: undefined
};
}
export function createQuery(): QueryList<any> {
return new QueryList();
}
export function dirtyParentQueries(view: ViewData) {
const queryIds = view.def.nodeMatchedQueries;
while (view.parent && isEmbeddedView(view)) {
let tplDef = view.parentNodeDef;
view = view.parent;
// content queries
const end = tplDef.index + tplDef.childCount;
for (let i = 0; i <= end; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & NodeFlags.TypeContentQuery) &&
(nodeDef.flags & NodeFlags.DynamicQuery) &&
(nodeDef.query.filterId & queryIds) === nodeDef.query.filterId) {
asQueryList(view, i).setDirty();
}
if ((nodeDef.flags & NodeFlags.TypeElement && i + nodeDef.childCount < tplDef.index) ||
!(nodeDef.childFlags & NodeFlags.TypeContentQuery) ||
!(nodeDef.childFlags & NodeFlags.DynamicQuery)) {
// skip elements that don't contain the template element or no query.
i += nodeDef.childCount;
}
}
}
// view queries
if (view.def.nodeFlags & NodeFlags.TypeViewQuery) {
for (let i = 0; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & NodeFlags.TypeViewQuery) && (nodeDef.flags & NodeFlags.DynamicQuery)) {
asQueryList(view, i).setDirty();
}
// only visit the root nodes
i += nodeDef.childCount;
}
}
}
export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
const queryList = asQueryList(view, nodeDef.index);
if (!queryList.dirty) {
return;
}
let directiveInstance: any;
let newValues: any[];
if (nodeDef.flags & NodeFlags.TypeContentQuery) {
const elementDef = nodeDef.parent.parent;
newValues = calcQueryValues(
view, elementDef.index, elementDef.index + elementDef.childCount, nodeDef.query, []);
directiveInstance = asProviderData(view, nodeDef.parent.index).instance;
} else if (nodeDef.flags & NodeFlags.TypeViewQuery) {
newValues = calcQueryValues(view, 0, view.def.nodes.length - 1, nodeDef.query, []);
directiveInstance = view.component;
}
queryList.reset(newValues);
const bindings = nodeDef.query.bindings;
let notify = false;
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i];
let boundValue: any;
switch (binding.bindingType) {
case QueryBindingType.First:
boundValue = queryList.first;
break;
case QueryBindingType.All:
boundValue = queryList;
notify = true;
break;
}
directiveInstance[binding.propName] = boundValue;
}
if (notify) {
queryList.notifyOnChanges();
}
}
function calcQueryValues(
view: ViewData, startIndex: number, endIndex: number, queryDef: QueryDef,
values: any[]): any[] {
for (let i = startIndex; i <= endIndex; i++) {
const nodeDef = view.def.nodes[i];
const valueType = nodeDef.matchedQueries[queryDef.id];
if (valueType != null) {
values.push(getQueryValue(view, nodeDef, valueType));
}
if (nodeDef.flags & NodeFlags.TypeElement && nodeDef.element.template &&
(nodeDef.element.template.nodeMatchedQueries & queryDef.filterId) === queryDef.filterId) {
// check embedded views that were attached at the place of their template.
const elementData = asElementData(view, i);
const embeddedViews = elementData.embeddedViews;
if (embeddedViews) {
for (let k = 0; k < embeddedViews.length; k++) {
const embeddedView = embeddedViews[k];
const dvc = declaredViewContainer(embeddedView);
if (dvc && dvc === elementData) {
calcQueryValues(embeddedView, 0, embeddedView.def.nodes.length - 1, queryDef, values);
}
}
}
const projectedViews = elementData.projectedViews;
if (projectedViews) {
for (let k = 0; k < projectedViews.length; k++) {
const projectedView = projectedViews[k];
calcQueryValues(projectedView, 0, projectedView.def.nodes.length - 1, queryDef, values);
}
}
}
if ((nodeDef.childMatchedQueries & queryDef.filterId) !== queryDef.filterId) {
// if no child matches the query, skip the children.
i += nodeDef.childCount;
}
}
return values;
}
export function getQueryValue(
view: ViewData, nodeDef: NodeDef, queryValueType: QueryValueType): any {
if (queryValueType != null) {
// a match
let value: any;
switch (queryValueType) {
case QueryValueType.RenderElement:
value = asElementData(view, nodeDef.index).renderElement;
break;
case QueryValueType.ElementRef:
value = new ElementRef(asElementData(view, nodeDef.index).renderElement);
break;
case QueryValueType.TemplateRef:
value = createTemplateRef(view, nodeDef);
break;
case QueryValueType.ViewContainerRef:
value = createViewContainerRef(view, nodeDef);
break;
case QueryValueType.Provider:
value = asProviderData(view, nodeDef.index).instance;
break;
}
return value;
}
}

View File

@ -0,0 +1,402 @@
/**
* @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 {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef} from '../change_detection/change_detection';
import {Injector} from '../di';
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef, InternalViewRef, ViewRef} from '../linker/view_ref';
import {Renderer as RendererV1, Renderer2} from '../render/api';
import {Type} from '../type';
import {VERSION} from '../version';
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types';
import {isComponentView, markParentViewsForCheck, renderNode, resolveViewDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util';
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachView} from './view_attach';
const EMPTY_CONTEXT = new Object();
export function createComponentFactory(
selector: string, componentType: Type<any>,
viewDefFactory: ViewDefinitionFactory): ComponentFactory<any> {
return new ComponentFactory_(selector, componentType, viewDefFactory);
}
export function getComponentViewDefinitionFactory(componentFactory: ComponentFactory<any>):
ViewDefinitionFactory {
return (componentFactory as ComponentFactory_).viewDefFactory;
}
class ComponentFactory_ extends ComponentFactory<any> {
/**
* @internal
*/
viewDefFactory: ViewDefinitionFactory;
constructor(
public selector: string, public componentType: Type<any>,
viewDefFactory: ViewDefinitionFactory) {
super();
this.viewDefFactory = viewDefFactory;
}
/**
* Creates a new component.
*/
create(
injector: Injector, projectableNodes: any[][] = null,
rootSelectorOrNode: string|any = null): ComponentRef<any> {
const viewDef = resolveViewDefinition(this.viewDefFactory);
const componentNodeIndex = viewDef.nodes[0].element.componentProvider.index;
const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, EMPTY_CONTEXT);
const component = asProviderData(view, componentNodeIndex).instance;
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
return new ComponentRef_(view, new ViewRef_(view), component);
}
}
class ComponentRef_ extends ComponentRef<any> {
private _elDef: NodeDef;
constructor(private _view: ViewData, private _viewRef: ViewRef, private _component: any) {
super();
this._elDef = this._view.def.nodes[0];
}
get location(): ElementRef {
return new ElementRef(asElementData(this._view, this._elDef.index).renderElement);
}
get injector(): Injector { return new Injector_(this._view, this._elDef); }
get instance(): any { return this._component; };
get hostView(): ViewRef { return this._viewRef; };
get changeDetectorRef(): ChangeDetectorRef { return this._viewRef; };
get componentType(): Type<any> { return <any>this._component.constructor; }
destroy(): void { this._viewRef.destroy(); }
onDestroy(callback: Function): void { this._viewRef.onDestroy(callback); }
}
export function createViewContainerRef(view: ViewData, elDef: NodeDef): ViewContainerRef {
return new ViewContainerRef_(view, elDef);
}
class ViewContainerRef_ implements ViewContainerRef {
private _data: ElementData;
constructor(private _view: ViewData, private _elDef: NodeDef) {
this._data = asElementData(_view, _elDef.index);
}
get element(): ElementRef { return new ElementRef(this._data.renderElement); }
get injector(): Injector { return new Injector_(this._view, this._elDef); }
get parentInjector(): Injector {
let view = this._view;
let elDef = this._elDef.parent;
while (!elDef && view) {
elDef = viewParentEl(view);
view = view.parent;
}
return view ? new Injector_(view, elDef) : this._view.root.injector;
}
clear(): void {
const len = this._data.embeddedViews.length;
for (let i = len - 1; i >= 0; i--) {
const view = detachEmbeddedView(this._data, i);
Services.destroyView(view);
}
}
get(index: number): ViewRef {
const view = this._data.embeddedViews[index];
if (view) {
const ref = new ViewRef_(view);
ref.attachToViewContainerRef(this);
return ref;
}
return null;
}
get length(): number { return this._data.embeddedViews.length; };
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number):
EmbeddedViewRef<C> {
const viewRef = templateRef.createEmbeddedView(context || <any>{});
this.insert(viewRef, index);
return viewRef;
}
createComponent<C>(
componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
projectableNodes?: any[][]): ComponentRef<C> {
const contextInjector = injector || this.parentInjector;
const componentRef = componentFactory.create(contextInjector, projectableNodes);
this.insert(componentRef.hostView, index);
return componentRef;
}
insert(viewRef: ViewRef, index?: number): ViewRef {
const viewRef_ = <ViewRef_>viewRef;
const viewData = viewRef_._view;
attachEmbeddedView(this._view, this._data, index, viewData);
viewRef_.attachToViewContainerRef(this);
return viewRef;
}
move(viewRef: ViewRef_, currentIndex: number): ViewRef {
const previousIndex = this._data.embeddedViews.indexOf(viewRef._view);
moveEmbeddedView(this._data, previousIndex, currentIndex);
return viewRef;
}
indexOf(viewRef: ViewRef): number {
return this._data.embeddedViews.indexOf((<ViewRef_>viewRef)._view);
}
remove(index?: number): void {
const viewData = detachEmbeddedView(this._data, index);
if (viewData) {
Services.destroyView(viewData);
}
}
detach(index?: number): ViewRef {
const view = detachEmbeddedView(this._data, index);
return view ? new ViewRef_(view) : null;
}
}
export function createChangeDetectorRef(view: ViewData): ChangeDetectorRef {
return new ViewRef_(view);
}
export class ViewRef_ implements EmbeddedViewRef<any>, InternalViewRef {
/** @internal */
_view: ViewData;
private _viewContainerRef: ViewContainerRef;
private _appRef: ApplicationRef;
constructor(_view: ViewData) {
this._view = _view;
this._viewContainerRef = null;
this._appRef = null;
}
get rootNodes(): any[] { return rootRenderNodes(this._view); }
get context() { return this._view.context; }
get destroyed(): boolean { return (this._view.state & ViewState.Destroyed) !== 0; }
markForCheck(): void { markParentViewsForCheck(this._view); }
detach(): void { this._view.state &= ~ViewState.ChecksEnabled; }
detectChanges(): void { Services.checkAndUpdateView(this._view); }
checkNoChanges(): void { Services.checkNoChangesView(this._view); }
reattach(): void { this._view.state |= ViewState.ChecksEnabled; }
onDestroy(callback: Function) {
if (!this._view.disposables) {
this._view.disposables = [];
}
this._view.disposables.push(<any>callback);
}
destroy() {
if (this._appRef) {
this._appRef.detachView(this);
} else if (this._viewContainerRef) {
this._viewContainerRef.detach(this._viewContainerRef.indexOf(this));
}
Services.destroyView(this._view);
}
detachFromAppRef() {
this._appRef = null;
renderDetachView(this._view);
Services.dirtyParentQueries(this._view);
}
attachToAppRef(appRef: ApplicationRef) {
if (this._viewContainerRef) {
throw new Error('This view is already attached to a ViewContainer!');
}
this._appRef = appRef;
}
attachToViewContainerRef(vcRef: ViewContainerRef) {
if (this._appRef) {
throw new Error('This view is already attached directly to the ApplicationRef!');
}
this._viewContainerRef = vcRef;
}
}
export function createTemplateRef(view: ViewData, def: NodeDef): TemplateRef<any> {
return new TemplateRef_(view, def);
}
class TemplateRef_ extends TemplateRef<any> {
constructor(private _parentView: ViewData, private _def: NodeDef) { super(); }
createEmbeddedView(context: any): EmbeddedViewRef<any> {
return new ViewRef_(Services.createEmbeddedView(this._parentView, this._def, context));
}
get elementRef(): ElementRef {
return new ElementRef(asElementData(this._parentView, this._def.index).renderElement);
}
}
export function createInjector(view: ViewData, elDef: NodeDef): Injector {
return new Injector_(view, elDef);
}
class Injector_ implements Injector {
constructor(private view: ViewData, private elDef: NodeDef) {}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
const allowPrivateServices = (this.elDef.flags & NodeFlags.ComponentView) !== 0;
return Services.resolveDep(
this.view, this.elDef, allowPrivateServices,
{flags: DepFlags.None, token, tokenKey: tokenKey(token)}, notFoundValue);
}
}
export function nodeValue(view: ViewData, index: number): any {
const def = view.def.nodes[index];
if (def.flags & NodeFlags.TypeElement) {
if (def.element.template) {
return createTemplateRef(view, def);
} else {
return asElementData(view, def.index).renderElement;
}
} else if (def.flags & NodeFlags.TypeText) {
return asTextData(view, def.index).renderText;
} else if (def.flags & (NodeFlags.CatProvider | NodeFlags.TypePipe)) {
return asProviderData(view, def.index).instance;
}
throw new Error(`Illegal state: read nodeValue for node index ${index}`);
}
export function createRendererV1(view: ViewData): RendererV1 {
return new RendererAdapter(view.renderer);
}
class RendererAdapter implements RendererV1 {
constructor(private delegate: Renderer2) {}
selectRootElement(selectorOrNode: string|Element): Element {
return this.delegate.selectRootElement(selectorOrNode);
}
createElement(parent: Element|DocumentFragment, namespaceAndName: string): Element {
const [ns, name] = splitNamespace(namespaceAndName);
const el = this.delegate.createElement(name, ns);
if (parent) {
this.delegate.appendChild(parent, el);
}
return el;
}
createViewRoot(hostElement: Element): Element|DocumentFragment { return hostElement; }
createTemplateAnchor(parentElement: Element|DocumentFragment): Comment {
const comment = this.delegate.createComment('');
if (parentElement) {
this.delegate.appendChild(parentElement, comment);
}
return comment;
}
createText(parentElement: Element|DocumentFragment, value: string): any {
const node = this.delegate.createText(value);
if (parentElement) {
this.delegate.appendChild(parentElement, node);
}
return node;
}
projectNodes(parentElement: Element|DocumentFragment, nodes: Node[]) {
for (let i = 0; i < nodes.length; i++) {
this.delegate.appendChild(parentElement, nodes[i]);
}
}
attachViewAfter(node: Node, viewRootNodes: Node[]) {
const parentElement = this.delegate.parentNode(node);
const nextSibling = this.delegate.nextSibling(node);
for (let i = 0; i < viewRootNodes.length; i++) {
this.delegate.insertBefore(parentElement, viewRootNodes[i], nextSibling);
}
}
detachView(viewRootNodes: (Element|Text|Comment)[]) {
for (let i = 0; i < viewRootNodes.length; i++) {
const node = viewRootNodes[i];
const parentElement = this.delegate.parentNode(node);
this.delegate.removeChild(parentElement, node);
}
}
destroyView(hostElement: Element|DocumentFragment, viewAllNodes: Node[]) {
for (let i = 0; i < viewAllNodes.length; i++) {
this.delegate.destroyNode(viewAllNodes[i]);
}
}
listen(renderElement: any, name: string, callback: Function): Function {
return this.delegate.listen(renderElement, name, <any>callback);
}
listenGlobal(target: string, name: string, callback: Function): Function {
return this.delegate.listen(target, name, <any>callback);
}
setElementProperty(
renderElement: Element|DocumentFragment, propertyName: string, propertyValue: any): void {
this.delegate.setProperty(renderElement, propertyName, propertyValue);
}
setElementAttribute(renderElement: Element, namespaceAndName: string, attributeValue: string):
void {
const [ns, name] = splitNamespace(namespaceAndName);
if (attributeValue != null) {
this.delegate.setAttribute(renderElement, name, attributeValue, ns);
} else {
this.delegate.removeAttribute(renderElement, name, ns);
}
}
setBindingDebugInfo(renderElement: Element, propertyName: string, propertyValue: string): void {}
setElementClass(renderElement: Element, className: string, isAdd: boolean): void {
if (isAdd) {
this.delegate.addClass(renderElement, className);
} else {
this.delegate.removeClass(renderElement, className);
}
}
setElementStyle(renderElement: HTMLElement, styleName: string, styleValue: string): void {
if (styleValue != null) {
this.delegate.setStyle(renderElement, styleName, styleValue, false, false);
} else {
this.delegate.removeStyle(renderElement, styleName, false);
}
}
invokeElementMethod(renderElement: Element, methodName: string, args: any[]): void {
(renderElement as any)[methodName].apply(renderElement, args);
}
setText(renderNode: Text, text: string): void { this.delegate.setValue(renderNode, text); }
animate(): any { throw new Error('Renderer.animate is no longer supported!'); }
}

View File

@ -0,0 +1,583 @@
/**
* @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 {isDevMode} from '../application_ref';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
import {Injector} from '../di';
import {Renderer2, RendererFactory2, RendererType2} from '../render/api';
import {Sanitizer, SecurityContext} from '../security';
import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {resolveDep} from './provider';
import {dirtyParentQueries, getQueryValue} from './query';
import {createInjector} from './refs';
import {ArgumentType, BindingType, CheckType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asPureExpressionData} from './types';
import {checkBinding, isComponentView, renderNode, viewParentEl} from './util';
import {checkAndUpdateNode, checkAndUpdateView, checkNoChangesNode, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
let initialized = false;
export function initServicesIfNeeded() {
if (initialized) {
return;
}
initialized = true;
const services = isDevMode() ? createDebugServices() : createProdServices();
Services.setCurrentNode = services.setCurrentNode;
Services.createRootView = services.createRootView;
Services.createEmbeddedView = services.createEmbeddedView;
Services.checkAndUpdateView = services.checkAndUpdateView;
Services.checkNoChangesView = services.checkNoChangesView;
Services.destroyView = services.destroyView;
Services.resolveDep = resolveDep;
Services.createDebugContext = services.createDebugContext;
Services.handleEvent = services.handleEvent;
Services.updateDirectives = services.updateDirectives;
Services.updateRenderer = services.updateRenderer;
Services.dirtyParentQueries = dirtyParentQueries;
}
function createProdServices() {
return {
setCurrentNode: () => {},
createRootView: createProdRootView,
createEmbeddedView: createEmbeddedView,
checkAndUpdateView: checkAndUpdateView,
checkNoChangesView: checkNoChangesView,
destroyView: destroyView,
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
handleEvent: (view: ViewData, nodeIndex: number, eventName: string, event: any) =>
view.def.handleEvent(view, nodeIndex, eventName, event),
updateDirectives: (view: ViewData, checkType: CheckType) => view.def.updateDirectives(
checkType === CheckType.CheckAndUpdate ? prodCheckAndUpdateNode :
prodCheckNoChangesNode,
view),
updateRenderer: (view: ViewData, checkType: CheckType) => view.def.updateRenderer(
checkType === CheckType.CheckAndUpdate ? prodCheckAndUpdateNode :
prodCheckNoChangesNode,
view),
};
}
function createDebugServices() {
return {
setCurrentNode: debugSetCurrentNode,
createRootView: debugCreateRootView,
createEmbeddedView: debugCreateEmbeddedView,
checkAndUpdateView: debugCheckAndUpdateView,
checkNoChangesView: debugCheckNoChangesView,
destroyView: debugDestroyView,
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
handleEvent: debugHandleEvent,
updateDirectives: debugUpdateDirectives,
updateRenderer: debugUpdateRenderer
};
}
function createProdRootView(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
def: ViewDefinition, context?: any): ViewData {
const rendererFactory: RendererFactory2 = injector.get(RendererFactory2);
return createRootView(
createRootData(injector, rendererFactory, projectableNodes, rootSelectorOrNode), def,
context);
}
function debugCreateRootView(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any,
def: ViewDefinition, context?: any): ViewData {
const rendererFactory: RendererFactory2 = injector.get(RendererFactory2);
const root = createRootData(
injector, new DebugRendererFactory2(rendererFactory), projectableNodes, rootSelectorOrNode);
return callWithDebugContext(DebugAction.create, createRootView, null, [root, def, context]);
}
function createRootData(
injector: Injector, rendererFactory: RendererFactory2, projectableNodes: any[][],
rootSelectorOrNode: any): RootData {
const sanitizer = injector.get(Sanitizer);
const renderer = rendererFactory.createRenderer(null, null);
return {
injector,
projectableNodes,
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer
};
}
function prodCheckAndUpdateNode(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any {
const nodeDef = view.def.nodes[nodeIndex];
checkAndUpdateNode(view, nodeDef, argStyle, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
return (nodeDef.flags & NodeFlags.CatPureExpression) ?
asPureExpressionData(view, nodeIndex).value :
undefined;
}
function prodCheckNoChangesNode(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any {
const nodeDef = view.def.nodes[nodeIndex];
checkNoChangesNode(view, nodeDef, argStyle, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
return (nodeDef.flags & NodeFlags.CatPureExpression) ?
asPureExpressionData(view, nodeIndex).value :
undefined;
}
function debugCreateEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
return callWithDebugContext(
DebugAction.create, createEmbeddedView, null, [parent, anchorDef, context]);
}
function debugCheckAndUpdateView(view: ViewData) {
return callWithDebugContext(DebugAction.detectChanges, checkAndUpdateView, null, [view]);
}
function debugCheckNoChangesView(view: ViewData) {
return callWithDebugContext(DebugAction.checkNoChanges, checkNoChangesView, null, [view]);
}
function debugDestroyView(view: ViewData) {
return callWithDebugContext(DebugAction.destroy, destroyView, null, [view]);
}
enum DebugAction {
create,
detectChanges,
checkNoChanges,
destroy,
handleEvent
}
let _currentAction: DebugAction;
let _currentView: ViewData;
let _currentNodeIndex: number;
function debugSetCurrentNode(view: ViewData, nodeIndex: number) {
_currentView = view;
_currentNodeIndex = nodeIndex;
}
function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string, event: any) {
debugSetCurrentNode(view, nodeIndex);
return callWithDebugContext(
DebugAction.handleEvent, view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
}
function debugUpdateDirectives(view: ViewData, checkType: CheckType) {
if (view.state & ViewState.Destroyed) {
throw viewDestroyedError(DebugAction[_currentAction]);
}
debugSetCurrentNode(view, nextDirectiveWithBinding(view, 0));
return view.def.updateDirectives(debugCheckDirectivesFn, view);
function debugCheckDirectivesFn(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
const nodeDef = view.def.nodes[nodeIndex];
if (checkType === CheckType.CheckAndUpdate) {
debugCheckAndUpdateNode(view, nodeDef, argStyle, values);
} else {
debugCheckNoChangesNode(view, nodeDef, argStyle, values);
}
if (nodeDef.flags & NodeFlags.TypeDirective) {
debugSetCurrentNode(view, nextDirectiveWithBinding(view, nodeIndex));
}
return (nodeDef.flags & NodeFlags.CatPureExpression) ?
asPureExpressionData(view, nodeDef.index).value :
undefined;
};
}
function debugUpdateRenderer(view: ViewData, checkType: CheckType) {
if (view.state & ViewState.Destroyed) {
throw viewDestroyedError(DebugAction[_currentAction]);
}
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, 0));
return view.def.updateRenderer(debugCheckRenderNodeFn, view);
function debugCheckRenderNodeFn(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
const nodeDef = view.def.nodes[nodeIndex];
if (checkType === CheckType.CheckAndUpdate) {
debugCheckAndUpdateNode(view, nodeDef, argStyle, values);
} else {
debugCheckNoChangesNode(view, nodeDef, argStyle, values);
}
if (nodeDef.flags & NodeFlags.CatRenderNode) {
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, nodeIndex));
}
return (nodeDef.flags & NodeFlags.CatPureExpression) ?
asPureExpressionData(view, nodeDef.index).value :
undefined;
}
}
function debugCheckAndUpdateNode(
view: ViewData, nodeDef: NodeDef, argStyle: ArgumentType, givenValues: any[]): void {
const changed = (<any>checkAndUpdateNode)(view, nodeDef, argStyle, ...givenValues);
if (changed) {
const values = argStyle === ArgumentType.Dynamic ? givenValues[0] : givenValues;
if (nodeDef.flags & (NodeFlags.TypeDirective | NodeFlags.TypeElement)) {
const bindingValues: {[key: string]: string} = {};
for (let i = 0; i < nodeDef.bindings.length; i++) {
const binding = nodeDef.bindings[i];
const value = values[i];
if ((binding.type === BindingType.ComponentHostProperty ||
binding.type === BindingType.DirectiveProperty)) {
bindingValues[normalizeDebugBindingName(binding.nonMinifiedName)] =
normalizeDebugBindingValue(value);
}
}
const elDef = nodeDef.flags & NodeFlags.TypeDirective ? nodeDef.parent : nodeDef;
const el = asElementData(view, elDef.index).renderElement;
if (!elDef.element.name) {
// a comment.
view.renderer.setValue(el, `bindings=${JSON.stringify(bindingValues, null, 2)}`);
} else {
// a regular element.
for (let attr in bindingValues) {
const value = bindingValues[attr];
if (value != null) {
view.renderer.setAttribute(el, attr, value);
} else {
view.renderer.removeAttribute(el, attr);
}
}
}
}
}
}
function debugCheckNoChangesNode(
view: ViewData, nodeDef: NodeDef, argStyle: ArgumentType, values: any[]): void {
(<any>checkNoChangesNode)(view, nodeDef, argStyle, ...values);
}
function normalizeDebugBindingName(name: string) {
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
name = camelCaseToDashCase(name.replace(/[$@]/g, '_'));
return `ng-reflect-${name}`;
}
const CAMEL_CASE_REGEXP = /([A-Z])/g;
function camelCaseToDashCase(input: string): string {
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
}
function normalizeDebugBindingValue(value: any): string {
try {
// Limit the size of the value as otherwise the DOM just gets polluted.
return value ? value.toString().slice(0, 30) : value;
} catch (e) {
return '[ERROR] Exception while trying to serialize the value';
}
}
function nextDirectiveWithBinding(view: ViewData, nodeIndex: number): number {
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i];
if (nodeDef.flags & NodeFlags.TypeDirective && nodeDef.bindings && nodeDef.bindings.length) {
return i;
}
}
return undefined;
}
function nextRenderNodeWithBinding(view: ViewData, nodeIndex: number): number {
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & NodeFlags.CatRenderNode) && nodeDef.bindings && nodeDef.bindings.length) {
return i;
}
}
return undefined;
}
class DebugContext_ implements DebugContext {
private nodeDef: NodeDef;
private elView: ViewData;
private elDef: NodeDef;
constructor(public view: ViewData, public nodeIndex: number) {
if (nodeIndex == null) {
this.nodeIndex = nodeIndex = 0;
}
this.nodeDef = view.def.nodes[nodeIndex];
let elDef = this.nodeDef;
let elView = view;
while (elDef && (elDef.flags & NodeFlags.TypeElement) === 0) {
elDef = elDef.parent;
}
if (!elDef) {
while (!elDef && elView) {
elDef = viewParentEl(elView);
elView = elView.parent;
}
}
this.elDef = elDef;
this.elView = elView;
}
private get elOrCompView() {
// Has to be done lazily as we use the DebugContext also during creation of elements...
return asElementData(this.elView, this.elDef.index).componentView || this.view;
}
get injector(): Injector { return createInjector(this.elView, this.elDef); }
get component(): any { return this.elOrCompView.component; }
get context(): any { return this.elOrCompView.context; }
get providerTokens(): any[] {
const tokens: any[] = [];
if (this.elDef) {
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
const childDef = this.elView.def.nodes[i];
if (childDef.flags & NodeFlags.CatProvider) {
tokens.push(childDef.provider.token);
}
i += childDef.childCount;
}
}
return tokens;
}
get references(): {[key: string]: any} {
const references: {[key: string]: any} = {};
if (this.elDef) {
collectReferences(this.elView, this.elDef, references);
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
const childDef = this.elView.def.nodes[i];
if (childDef.flags & NodeFlags.CatProvider) {
collectReferences(this.elView, childDef, references);
}
i += childDef.childCount;
}
}
return references;
}
get source(): string {
if (this.nodeDef.flags & NodeFlags.TypeText) {
return this.nodeDef.text.source;
} else {
return this.elDef.element.source;
}
}
get componentRenderElement() {
const elData = findHostElement(this.elOrCompView);
return elData ? elData.renderElement : undefined;
}
get renderNode(): any {
return this.nodeDef.flags & NodeFlags.TypeText ? renderNode(this.view, this.nodeDef) :
renderNode(this.elView, this.elDef);
}
}
function findHostElement(view: ViewData): ElementData {
while (view && !isComponentView(view)) {
view = view.parent;
}
if (view.parent) {
return asElementData(view.parent, viewParentEl(view).index);
}
return undefined;
}
function collectReferences(view: ViewData, nodeDef: NodeDef, references: {[key: string]: any}) {
for (let refName in nodeDef.references) {
references[refName] = getQueryValue(view, nodeDef, nodeDef.references[refName]);
}
}
function callWithDebugContext(action: DebugAction, fn: any, self: any, args: any[]) {
const oldAction = _currentAction;
const oldView = _currentView;
const oldNodeIndex = _currentNodeIndex;
try {
_currentAction = action;
const result = fn.apply(self, args);
_currentView = oldView;
_currentNodeIndex = oldNodeIndex;
_currentAction = oldAction;
return result;
} catch (e) {
if (isViewDebugError(e) || !_currentView) {
throw e;
}
_currentView.state |= ViewState.Errored;
throw viewWrappedDebugError(e, getCurrentDebugContext());
}
}
export function getCurrentDebugContext(): DebugContext {
return _currentView ? new DebugContext_(_currentView, _currentNodeIndex) : null;
}
class DebugRendererFactory2 implements RendererFactory2 {
constructor(private delegate: RendererFactory2) {}
createRenderer(element: any, renderData: RendererType2): Renderer2 {
return new DebugRenderer2(this.delegate.createRenderer(element, renderData));
}
}
class DebugRenderer2 implements Renderer2 {
constructor(private delegate: Renderer2) {}
get data() { return this.delegate.data; }
destroyNode(node: any) {
removeDebugNodeFromIndex(getDebugNode(node));
if (this.delegate.destroyNode) {
this.delegate.destroyNode(node);
}
}
destroy() { this.delegate.destroy(); }
createElement(name: string, namespace?: string): any {
const el = this.delegate.createElement(name, namespace);
const debugCtx = getCurrentDebugContext();
if (debugCtx) {
const debugEl = new DebugElement(el, null, debugCtx);
debugEl.name = name;
indexDebugNode(debugEl);
}
return el;
}
createComment(value: string): any {
const comment = this.delegate.createComment(value);
const debugCtx = getCurrentDebugContext();
if (debugCtx) {
indexDebugNode(new DebugNode(comment, null, debugCtx));
}
return comment;
}
createText(value: string): any {
const text = this.delegate.createText(value);
const debugCtx = getCurrentDebugContext();
if (debugCtx) {
indexDebugNode(new DebugNode(text, null, debugCtx));
}
return text;
}
appendChild(parent: any, newChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(newChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.addChild(debugChildEl);
}
this.delegate.appendChild(parent, newChild);
}
insertBefore(parent: any, newChild: any, refChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(newChild);
const debugRefEl = getDebugNode(refChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.insertBefore(debugRefEl, debugChildEl);
}
this.delegate.insertBefore(parent, newChild, refChild);
}
removeChild(parent: any, oldChild: any): void {
const debugEl = getDebugNode(parent);
const debugChildEl = getDebugNode(oldChild);
if (debugEl && debugChildEl && debugEl instanceof DebugElement) {
debugEl.removeChild(debugChildEl);
}
this.delegate.removeChild(parent, oldChild);
}
selectRootElement(selectorOrNode: string|any): any {
const el = this.delegate.selectRootElement(selectorOrNode);
const debugCtx = getCurrentDebugContext();
if (debugCtx) {
indexDebugNode(new DebugElement(el, null, debugCtx));
}
return el;
}
setAttribute(el: any, name: string, value: string, namespace?: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
const fullName = namespace ? namespace + ':' + name : name;
debugEl.attributes[fullName] = value;
}
this.delegate.setAttribute(el, name, value, namespace);
}
removeAttribute(el: any, name: string, namespace?: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
const fullName = namespace ? namespace + ':' + name : name;
debugEl.attributes[fullName] = null;
}
this.delegate.removeAttribute(el, name, namespace);
}
addClass(el: any, name: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.classes[name] = true;
}
this.delegate.addClass(el, name);
}
removeClass(el: any, name: string): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.classes[name] = false;
}
this.delegate.removeClass(el, name);
}
setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean):
void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.styles[style] = value;
}
this.delegate.setStyle(el, style, value, hasVendorPrefix, hasImportant);
}
removeStyle(el: any, style: string, hasVendorPrefix: boolean): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.styles[style] = null;
}
this.delegate.removeStyle(el, style, hasVendorPrefix);
}
setProperty(el: any, name: string, value: any): void {
const debugEl = getDebugNode(el);
if (debugEl && debugEl instanceof DebugElement) {
debugEl.properties[name] = value;
}
this.delegate.setProperty(el, name, value);
}
listen(
target: 'document'|'windows'|'body'|any, eventName: string,
callback: (event: any) => boolean): () => void {
if (typeof target !== 'string') {
const debugEl = getDebugNode(target);
if (debugEl) {
debugEl.listeners.push(new EventListener(eventName, callback));
}
}
return this.delegate.listen(target, eventName, callback);
}
parentNode(node: any): any { return this.delegate.parentNode(node); }
nextSibling(node: any): any { return this.delegate.nextSibling(node); }
setValue(node: any, value: string): void { return this.delegate.setValue(node, value); }
}

View File

@ -0,0 +1,126 @@
/**
* @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 {isDevMode} from '../application_ref';
import {looseIdentical} from '../util';
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
import {checkAndUpdateBinding, getParentRenderElement, sliceErrorStack} from './util';
export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
// skip the call to sliceErrorStack itself + the call to this function.
const source = isDevMode() ? sliceErrorStack(2, 3) : '';
const bindings: BindingDef[] = new Array(constants.length - 1);
for (let i = 1; i < constants.length; i++) {
bindings[i - 1] = {
type: BindingType.TextInterpolation,
name: undefined,
ns: undefined,
nonMinifiedName: undefined,
securityContext: undefined,
suffix: constants[i]
};
}
const flags = NodeFlags.TypeText;
return {
// will bet set by the view definition
index: undefined,
parent: undefined,
renderParent: undefined,
bindingIndex: undefined,
outputIndex: undefined,
// regular values
flags,
childFlags: 0,
directChildFlags: 0,
childMatchedQueries: 0,
matchedQueries: {},
matchedQueryIds: 0,
references: {}, ngContentIndex,
childCount: 0, bindings,
outputs: [],
element: undefined,
provider: undefined,
text: {prefix: constants[0], source},
query: undefined,
ngContent: undefined
};
}
export function createText(view: ViewData, renderHost: any, def: NodeDef): TextData {
let renderNode: any;
const renderer = view.renderer;
renderNode = renderer.createText(def.text.prefix);
const parentEl = getParentRenderElement(view, renderHost, def);
if (parentEl) {
renderer.appendChild(parentEl, renderNode);
}
return {renderText: renderNode};
}
export function checkAndUpdateTextInline(
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any): boolean {
let changed = false;
const bindings = def.bindings;
const bindLen = bindings.length;
if (bindLen > 0 && checkAndUpdateBinding(view, def, 0, v0)) changed = true;
if (bindLen > 1 && checkAndUpdateBinding(view, def, 1, v1)) changed = true;
if (bindLen > 2 && checkAndUpdateBinding(view, def, 2, v2)) changed = true;
if (bindLen > 3 && checkAndUpdateBinding(view, def, 3, v3)) changed = true;
if (bindLen > 4 && checkAndUpdateBinding(view, def, 4, v4)) changed = true;
if (bindLen > 5 && checkAndUpdateBinding(view, def, 5, v5)) changed = true;
if (bindLen > 6 && checkAndUpdateBinding(view, def, 6, v6)) changed = true;
if (bindLen > 7 && checkAndUpdateBinding(view, def, 7, v7)) changed = true;
if (bindLen > 8 && checkAndUpdateBinding(view, def, 8, v8)) changed = true;
if (bindLen > 9 && checkAndUpdateBinding(view, def, 9, v9)) changed = true;
if (changed) {
let value = def.text.prefix;
if (bindLen > 0) value += _addInterpolationPart(v0, bindings[0]);
if (bindLen > 1) value += _addInterpolationPart(v1, bindings[1]);
if (bindLen > 2) value += _addInterpolationPart(v2, bindings[2]);
if (bindLen > 3) value += _addInterpolationPart(v3, bindings[3]);
if (bindLen > 4) value += _addInterpolationPart(v4, bindings[4]);
if (bindLen > 5) value += _addInterpolationPart(v5, bindings[5]);
if (bindLen > 6) value += _addInterpolationPart(v6, bindings[6]);
if (bindLen > 7) value += _addInterpolationPart(v7, bindings[7]);
if (bindLen > 8) value += _addInterpolationPart(v8, bindings[8]);
if (bindLen > 9) value += _addInterpolationPart(v9, bindings[9]);
const renderNode = asTextData(view, def.index).renderText;
view.renderer.setValue(renderNode, value);
}
return changed;
}
export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values: any[]): boolean {
const bindings = def.bindings;
let changed = false;
for (let i = 0; i < values.length; i++) {
// Note: We need to loop over all values, so that
// the old values are updates as well!
if (checkAndUpdateBinding(view, def, i, values[i])) {
changed = true;
}
}
if (changed) {
let value = '';
for (let i = 0; i < values.length; i++) {
value = value + _addInterpolationPart(values[i], bindings[i]);
}
value = def.text.prefix + value;
const renderNode = asTextData(view, def.index).renderText;
view.renderer.setValue(renderNode, value);
}
return changed;
}
function _addInterpolationPart(value: any, binding: BindingDef): string {
const valueStr = value != null ? value.toString() : '';
return valueStr + binding.suffix;
}

View File

@ -0,0 +1,472 @@
/**
* @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 {PipeTransform} from '../change_detection/change_detection';
import {Injector} from '../di';
import {ComponentRef} from '../linker/component_factory';
import {QueryList} from '../linker/query_list';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {ViewRef} from '../linker/view_ref';
import {ViewEncapsulation} from '../metadata/view';
import {Renderer2, RendererFactory2, RendererType2} from '../render/api';
import {Sanitizer, SecurityContext} from '../security';
// -------------------------------------
// Defs
// -------------------------------------
export interface ViewDefinition {
flags: ViewFlags;
updateDirectives: ViewUpdateFn;
updateRenderer: ViewUpdateFn;
handleEvent: ViewHandleEventFn;
/**
* Order: Depth first.
* Especially providers are before elements / anchors.
*/
nodes: NodeDef[];
/** aggregated NodeFlags for all nodes **/
nodeFlags: NodeFlags;
rootNodeFlags: NodeFlags;
lastRenderRootNode: NodeDef;
bindingCount: number;
outputCount: number;
/**
* Binary or of all query ids that are matched by one of the nodes.
* This includes query ids from templates as well.
* Used as a bloom filter.
*/
nodeMatchedQueries: number;
}
export type ViewDefinitionFactory = () => ViewDefinition;
export type ViewUpdateFn = (check: NodeCheckFn, view: ViewData) => void;
// helper functions to create an overloaded function type.
export interface NodeCheckFn {
(view: ViewData, nodeIndex: number, argStyle: ArgumentType.Dynamic, values: any[]): any;
(view: ViewData, nodeIndex: number, argStyle: ArgumentType.Inline, v0?: any, v1?: any, v2?: any,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any;
}
export type ViewHandleEventFn =
(view: ViewData, nodeIndex: number, eventName: string, event: any) => boolean;
export const enum ArgumentType {Inline, Dynamic}
/**
* Bitmask for ViewDefintion.flags.
*/
export const enum ViewFlags {
None = 0,
OnPush = 1 << 1,
}
/**
* A node definition in the view.
*
* Note: We use one type for all nodes so that loops that loop over all nodes
* of a ViewDefinition stay monomorphic!
*/
export interface NodeDef {
flags: NodeFlags;
index: number;
parent: NodeDef;
renderParent: NodeDef;
/** this is checked against NgContentDef.index to find matched nodes */
ngContentIndex: number;
/** number of transitive children */
childCount: number;
/** aggregated NodeFlags for all transitive children (does not include self) **/
childFlags: NodeFlags;
/** aggregated NodeFlags for all direct children (does not include self) **/
directChildFlags: NodeFlags;
bindingIndex: number;
bindings: BindingDef[];
outputIndex: number;
outputs: OutputDef[];
/**
* references that the user placed on the element
*/
references: {[refId: string]: QueryValueType};
/**
* ids and value types of all queries that are matched by this node.
*/
matchedQueries: {[queryId: number]: QueryValueType};
/** Binary or of all matched query ids of this node. */
matchedQueryIds: number;
/**
* Binary or of all query ids that are matched by one of the children.
* This includes query ids from templates as well.
* Used as a bloom filter.
*/
childMatchedQueries: number;
element: ElementDef;
provider: ProviderDef;
text: TextDef;
query: QueryDef;
ngContent: NgContentDef;
}
/**
* Bitmask for NodeDef.flags.
* Naming convention:
* - `Type...`: flags that are mutually exclusive
* - `Cat...`: union of multiple `Type...` (short for category).
*/
export const enum NodeFlags {
None = 0,
TypeElement = 1 << 0,
TypeText = 1 << 1,
CatRenderNode = TypeElement | TypeText,
TypeNgContent = 1 << 2,
TypePipe = 1 << 3,
TypePureArray = 1 << 4,
TypePureObject = 1 << 5,
TypePurePipe = 1 << 6,
CatPureExpression = TypePureArray | TypePureObject | TypePurePipe,
TypeValueProvider = 1 << 7,
TypeClassProvider = 1 << 8,
TypeFactoryProvider = 1 << 9,
TypeUseExistingProvider = 1 << 10,
LazyProvider = 1 << 11,
PrivateProvider = 1 << 12,
TypeDirective = 1 << 13,
Component = 1 << 14,
CatProvider = TypeValueProvider | TypeClassProvider | TypeFactoryProvider |
TypeUseExistingProvider | TypeDirective,
OnInit = 1 << 15,
OnDestroy = 1 << 16,
DoCheck = 1 << 17,
OnChanges = 1 << 18,
AfterContentInit = 1 << 19,
AfterContentChecked = 1 << 20,
AfterViewInit = 1 << 21,
AfterViewChecked = 1 << 22,
EmbeddedViews = 1 << 23,
ComponentView = 1 << 24,
TypeContentQuery = 1 << 25,
TypeViewQuery = 1 << 26,
StaticQuery = 1 << 27,
DynamicQuery = 1 << 28,
CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values...
Types = CatRenderNode | TypeNgContent | TypePipe | CatPureExpression | CatProvider | CatQuery
}
export interface BindingDef {
type: BindingType;
ns: string;
name: string;
nonMinifiedName: string;
securityContext: SecurityContext;
suffix: string;
}
export const enum BindingType {
ElementAttribute,
ElementClass,
ElementStyle,
ElementProperty,
ComponentHostProperty,
DirectiveProperty,
TextInterpolation,
PureExpressionProperty
}
export interface OutputDef {
type: OutputType;
target: 'window'|'document'|'body'|'component';
eventName: string;
propName: string;
}
export const enum OutputType {ElementOutput, DirectiveOutput}
export const enum QueryValueType {
ElementRef,
RenderElement,
TemplateRef,
ViewContainerRef,
Provider
}
export interface ElementDef {
name: string;
ns: string;
/** ns, name, value */
attrs: [string, string, string][];
template: ViewDefinition;
componentProvider: NodeDef;
componentRendererType: RendererType2;
// closure to allow recursive components
componentView: ViewDefinitionFactory;
/**
* visible public providers for DI in the view,
* as see from this element. This does not include private providers.
*/
publicProviders: {[tokenKey: string]: NodeDef};
/**
* same as visiblePublicProviders, but also includes private providers
* that are located on this element.
*/
allProviders: {[tokenKey: string]: NodeDef};
source: string;
handleEvent: ElementHandleEventFn;
}
export type ElementHandleEventFn = (view: ViewData, eventName: string, event: any) => boolean;
export interface ProviderDef {
token: any;
tokenKey: string;
value: any;
deps: DepDef[];
}
export interface DepDef {
flags: DepFlags;
token: any;
tokenKey: string;
}
/**
* Bitmask for DI flags
*/
export const enum DepFlags {
None = 0,
SkipSelf = 1 << 0,
Optional = 1 << 1,
Value = 2 << 2,
}
export interface TextDef {
prefix: string;
source: string;
}
export interface QueryDef {
id: number;
// variant of the id that can be used to check against NodeDef.matchedQueryIds, ...
filterId: number;
bindings: QueryBindingDef[];
}
export interface QueryBindingDef {
propName: string;
bindingType: QueryBindingType;
}
export const enum QueryBindingType {First, All}
export interface NgContentDef {
/**
* this index is checked against NodeDef.ngContentIndex to find the nodes
* that are matched by this ng-content.
* Note that a NodeDef with an ng-content can be reprojected, i.e.
* have a ngContentIndex on its own.
*/
index: number;
}
// -------------------------------------
// Data
// -------------------------------------
/**
* View instance data.
* Attention: Adding fields to this is performance sensitive!
*/
export interface ViewData {
def: ViewDefinition;
root: RootData;
renderer: Renderer2;
// index of component provider / anchor.
parentNodeDef: NodeDef;
parent: ViewData;
viewContainerParent: ViewData;
component: any;
context: any;
// Attention: Never loop over this, as this will
// create a polymorphic usage site.
// Instead: Always loop over ViewDefinition.nodes,
// and call the right accessor (e.g. `elementData`) based on
// the NodeType.
nodes: {[key: number]: NodeData};
state: ViewState;
oldValues: any[];
disposables: DisposableFn[];
}
/**
* Bitmask of states
*/
export const enum ViewState {
FirstCheck = 1 << 0,
ChecksEnabled = 1 << 1,
Errored = 1 << 2,
Destroyed = 1 << 3
}
export type DisposableFn = () => void;
/**
* Node instance data.
*
* We have a separate type per NodeType to save memory
* (TextData | ElementData | ProviderData | PureExpressionData | QueryList<any>)
*
* To keep our code monomorphic,
* we prohibit using `NodeData` directly but enforce the use of accessors (`asElementData`, ...).
* This way, no usage site can get a `NodeData` from view.nodes and then use it for different
* purposes.
*/
export class NodeData { private __brand: any; }
/**
* Data for an instantiated NodeType.Text.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface TextData { renderText: any; }
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asTextData(view: ViewData, index: number): TextData {
return <any>view.nodes[index];
}
/**
* Data for an instantiated NodeType.Element.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface ElementData {
renderElement: any;
componentView: ViewData;
embeddedViews: ViewData[];
// views that have been created from the template
// of this element,
// but inserted into the embeddedViews of another element.
// By default, this is undefined.
projectedViews: ViewData[];
}
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asElementData(view: ViewData, index: number): ElementData {
return <any>view.nodes[index];
}
/**
* Data for an instantiated NodeType.Provider.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface ProviderData { instance: any; }
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asProviderData(view: ViewData, index: number): ProviderData {
return <any>view.nodes[index];
}
/**
* Data for an instantiated NodeType.PureExpression.
*
* Attention: Adding fields to this is performance sensitive!
*/
export interface PureExpressionData { value: any; }
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asPureExpressionData(view: ViewData, index: number): PureExpressionData {
return <any>view.nodes[index];
}
/**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic.
*/
export function asQueryList(view: ViewData, index: number): QueryList<any> {
return <any>view.nodes[index];
}
export interface RootData {
injector: Injector;
projectableNodes: any[][];
selectorOrNode: any;
renderer: Renderer2;
rendererFactory: RendererFactory2;
sanitizer: Sanitizer;
}
export abstract class DebugContext {
abstract get view(): ViewData;
abstract get nodeIndex(): number;
abstract get injector(): Injector;
abstract get component(): any;
abstract get providerTokens(): any[];
abstract get references(): {[key: string]: any};
abstract get context(): any;
abstract get source(): string;
abstract get componentRenderElement(): any;
abstract get renderNode(): any;
}
// -------------------------------------
// Other
// -------------------------------------
export const enum CheckType {CheckAndUpdate, CheckNoChanges}
export interface Services {
setCurrentNode(view: ViewData, nodeIndex: number): void;
createRootView(
injector: Injector, projectableNodes: any[][], rootSelectorOrNode: string|any,
def: ViewDefinition, context?: any): ViewData;
createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData;
checkAndUpdateView(view: ViewData): void;
checkNoChangesView(view: ViewData): void;
destroyView(view: ViewData): void;
resolveDep(
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
notFoundValue?: any): any;
createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
handleEvent: ViewHandleEventFn;
updateDirectives: (view: ViewData, checkType: CheckType) => void;
updateRenderer: (view: ViewData, checkType: CheckType) => void;
dirtyParentQueries: (view: ViewData) => void;
}
/**
* This object is used to prevent cycles in the source files and to have a place where
* debug mode can hook it. It is lazily filled when `isDevMode` is known.
*/
export const Services: Services = {
setCurrentNode: undefined,
createRootView: undefined,
createEmbeddedView: undefined,
checkAndUpdateView: undefined,
checkNoChangesView: undefined,
destroyView: undefined,
resolveDep: undefined,
createDebugContext: undefined,
handleEvent: undefined,
updateDirectives: undefined,
updateRenderer: undefined,
dirtyParentQueries: undefined,
};

View File

@ -0,0 +1,392 @@
/**
* @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 {isDevMode} from '../application_ref';
import {WrappedValue, devModeEqual} from '../change_detection/change_detection';
import {SimpleChange} from '../change_detection/change_detection_util';
import {Injector} from '../di';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {ViewRef} from '../linker/view_ref';
import {ViewEncapsulation} from '../metadata/view';
import {Renderer, RendererType2} from '../render/api';
import {looseIdentical, stringify} from '../util';
import {expressionChangedAfterItHasBeenCheckedError, isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {DebugContext, ElementData, NodeData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asProviderData, asTextData} from './types';
const _tokenKeyCache = new Map<any, string>();
export function tokenKey(token: any): string {
let key = _tokenKeyCache.get(token);
if (!key) {
key = stringify(token) + '_' + _tokenKeyCache.size;
_tokenKeyCache.set(token, key);
}
return key;
}
let unwrapCounter = 0;
export function unwrapValue(value: any): any {
if (value instanceof WrappedValue) {
value = value.wrapped;
unwrapCounter++;
}
return value;
}
let _renderCompCount = 0;
export function createRendererType2(values: {
styles: (string | any[])[],
encapsulation: ViewEncapsulation,
data: {[kind: string]: any[]}
}): RendererType2 {
const isFilled = values && (values.encapsulation !== ViewEncapsulation.None ||
values.styles.length || Object.keys(values.data).length);
if (isFilled) {
const id = `c${_renderCompCount++}`;
return {id: id, styles: values.styles, encapsulation: values.encapsulation, data: values.data};
} else {
return null;
}
}
export function checkBinding(
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
const oldValues = view.oldValues;
if (unwrapCounter > 0 || !!(view.state & ViewState.FirstCheck) ||
!looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) {
unwrapCounter = 0;
return true;
}
return false;
}
export function checkAndUpdateBinding(
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
if (checkBinding(view, def, bindingIdx, value)) {
view.oldValues[def.bindingIndex + bindingIdx] = value;
return true;
}
return false;
}
export function checkBindingNoChanges(
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
if (unwrapCounter || (view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
unwrapCounter = 0;
throw expressionChangedAfterItHasBeenCheckedError(
Services.createDebugContext(view, def.index), oldValue, value,
(view.state & ViewState.FirstCheck) !== 0);
}
}
export function markParentViewsForCheck(view: ViewData) {
let currView = view;
while (currView) {
if (currView.def.flags & ViewFlags.OnPush) {
currView.state |= ViewState.ChecksEnabled;
}
currView = currView.viewContainerParent || currView.parent;
}
}
export function dispatchEvent(
view: ViewData, nodeIndex: number, eventName: string, event: any): boolean {
markParentViewsForCheck(view);
return Services.handleEvent(view, nodeIndex, eventName, event);
}
export function declaredViewContainer(view: ViewData): ElementData {
if (view.parent) {
const parentView = view.parent;
return asElementData(parentView, view.parentNodeDef.index);
}
return undefined;
}
/**
* for component views, this is the host element.
* for embedded views, this is the index of the parent node
* that contains the view container.
*/
export function viewParentEl(view: ViewData): NodeDef {
const parentView = view.parent;
if (parentView) {
return view.parentNodeDef.parent;
} else {
return null;
}
}
export function renderNode(view: ViewData, def: NodeDef): any {
switch (def.flags & NodeFlags.Types) {
case NodeFlags.TypeElement:
return asElementData(view, def.index).renderElement;
case NodeFlags.TypeText:
return asTextData(view, def.index).renderText;
}
}
export function elementEventFullName(target: string, name: string): string {
return target ? `${target}:${name}` : name;
}
export function isComponentView(view: ViewData): boolean {
return !!view.parent && !!(view.parentNodeDef.flags & NodeFlags.Component);
}
export function isEmbeddedView(view: ViewData): boolean {
return !!view.parent && !(view.parentNodeDef.flags & NodeFlags.Component);
}
export function filterQueryId(queryId: number): number {
return 1 << (queryId % 32);
}
export function splitMatchedQueriesDsl(matchedQueriesDsl: [string | number, QueryValueType][]): {
matchedQueries: {[queryId: string]: QueryValueType},
references: {[refId: string]: QueryValueType},
matchedQueryIds: number
} {
const matchedQueries: {[queryId: string]: QueryValueType} = {};
let matchedQueryIds = 0;
const references: {[refId: string]: QueryValueType} = {};
if (matchedQueriesDsl) {
matchedQueriesDsl.forEach(([queryId, valueType]) => {
if (typeof queryId === 'number') {
matchedQueries[queryId] = valueType;
matchedQueryIds |= filterQueryId(queryId);
} else {
references[queryId] = valueType;
}
});
}
return {matchedQueries, references, matchedQueryIds};
}
export function getParentRenderElement(view: ViewData, renderHost: any, def: NodeDef): any {
let renderParent = def.renderParent;
if (renderParent) {
if ((renderParent.flags & NodeFlags.TypeElement) === 0 ||
(renderParent.flags & NodeFlags.ComponentView) === 0 ||
(renderParent.element.componentRendererType &&
renderParent.element.componentRendererType.encapsulation === ViewEncapsulation.Native)) {
// only children of non components, or children of components with native encapsulation should
// be attached.
return asElementData(view, def.renderParent.index).renderElement;
}
} else {
return renderHost;
}
}
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory);
if (!value) {
value = factory();
VIEW_DEFINITION_CACHE.set(factory, value);
}
return value;
}
export function sliceErrorStack(start: number, end: number): string {
let err: any;
try {
throw new Error();
} catch (e) {
err = e;
}
const stack = err.stack || '';
const lines = stack.split('\n');
if (lines[0].startsWith('Error')) {
// Chrome always adds the message to the stack as well...
start++;
end++;
}
return lines.slice(start, end).join('\n');
}
export function rootRenderNodes(view: ViewData): any[] {
const renderNodes: any[] = [];
visitRootRenderNodes(view, RenderNodeAction.Collect, undefined, undefined, renderNodes);
return renderNodes;
}
export enum RenderNodeAction {
Collect,
AppendChild,
InsertBefore,
RemoveChild
}
export function visitRootRenderNodes(
view: ViewData, action: RenderNodeAction, parentNode: any, nextSibling: any, target: any[]) {
// We need to re-compute the parent node in case the nodes have been moved around manually
if (action === RenderNodeAction.RemoveChild) {
parentNode = view.renderer.parentNode(renderNode(view, view.def.lastRenderRootNode));
}
visitSiblingRenderNodes(
view, action, 0, view.def.nodes.length - 1, parentNode, nextSibling, target);
}
export function visitSiblingRenderNodes(
view: ViewData, action: RenderNodeAction, startIndex: number, endIndex: number, parentNode: any,
nextSibling: any, target: any[]) {
for (let i = startIndex; i <= endIndex; i++) {
const nodeDef = view.def.nodes[i];
if (nodeDef.flags & (NodeFlags.TypeElement | NodeFlags.TypeText | NodeFlags.TypeNgContent)) {
visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target);
}
// jump to next sibling
i += nodeDef.childCount;
}
}
export function visitProjectedRenderNodes(
view: ViewData, ngContentIndex: number, action: RenderNodeAction, parentNode: any,
nextSibling: any, target: any[]) {
let compView = view;
while (compView && !isComponentView(compView)) {
compView = compView.parent;
}
const hostView = compView.parent;
const hostElDef = viewParentEl(compView);
const startIndex = hostElDef.index + 1;
const endIndex = hostElDef.index + hostElDef.childCount;
for (let i = startIndex; i <= endIndex; i++) {
const nodeDef = hostView.def.nodes[i];
if (nodeDef.ngContentIndex === ngContentIndex) {
visitRenderNode(hostView, nodeDef, action, parentNode, nextSibling, target);
}
// jump to next sibling
i += nodeDef.childCount;
}
if (!hostView.parent) {
// a root view
const projectedNodes = view.root.projectableNodes[ngContentIndex];
if (projectedNodes) {
for (let i = 0; i < projectedNodes.length; i++) {
execRenderNodeAction(view, projectedNodes[i], action, parentNode, nextSibling, target);
}
}
}
}
function visitRenderNode(
view: ViewData, nodeDef: NodeDef, action: RenderNodeAction, parentNode: any, nextSibling: any,
target: any[]) {
if (nodeDef.flags & NodeFlags.TypeNgContent) {
visitProjectedRenderNodes(
view, nodeDef.ngContent.index, action, parentNode, nextSibling, target);
} else {
const rn = renderNode(view, nodeDef);
execRenderNodeAction(view, rn, action, parentNode, nextSibling, target);
if (nodeDef.flags & NodeFlags.EmbeddedViews) {
const embeddedViews = asElementData(view, nodeDef.index).embeddedViews;
if (embeddedViews) {
for (let k = 0; k < embeddedViews.length; k++) {
visitRootRenderNodes(embeddedViews[k], action, parentNode, nextSibling, target);
}
}
}
if (nodeDef.flags & NodeFlags.TypeElement && !nodeDef.element.name) {
visitSiblingRenderNodes(
view, action, nodeDef.index + 1, nodeDef.index + nodeDef.childCount, parentNode,
nextSibling, target);
}
}
}
function execRenderNodeAction(
view: ViewData, renderNode: any, action: RenderNodeAction, parentNode: any, nextSibling: any,
target: any[]) {
const renderer = view.renderer;
switch (action) {
case RenderNodeAction.AppendChild:
renderer.appendChild(parentNode, renderNode);
break;
case RenderNodeAction.InsertBefore:
renderer.insertBefore(parentNode, renderNode, nextSibling);
break;
case RenderNodeAction.RemoveChild:
renderer.removeChild(parentNode, renderNode);
break;
case RenderNodeAction.Collect:
target.push(renderNode);
break;
}
}
const NS_PREFIX_RE = /^:([^:]+):(.+)$/;
export function splitNamespace(name: string): string[] {
if (name[0] === ':') {
const match = name.match(NS_PREFIX_RE);
return [match[1], match[2]];
}
return ['', name];
}
export function interpolate(valueCount: number, constAndInterp: string[]): string {
let result = '';
for (let i = 0; i < valueCount * 2; i = i + 2) {
result = result + constAndInterp[i] + _toStringWithNull(constAndInterp[i + 1]);
}
return result + constAndInterp[valueCount * 2];
}
export function inlineInterpolate(
valueCount: number, c0: string, a1: any, c1: string, a2?: any, c2?: string, a3?: any,
c3?: string, a4?: any, c4?: string, a5?: any, c5?: string, a6?: any, c6?: string, a7?: any,
c7?: string, a8?: any, c8?: string, a9?: any, c9?: string): string {
switch (valueCount) {
case 1:
return c0 + _toStringWithNull(a1) + c1;
case 2:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2;
case 3:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
c3;
case 4:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
c3 + _toStringWithNull(a4) + c4;
case 5:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5;
case 6:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) + c6;
case 7:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) +
c6 + _toStringWithNull(a7) + c7;
case 8:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) +
c6 + _toStringWithNull(a7) + c7 + _toStringWithNull(a8) + c8;
case 9:
return c0 + _toStringWithNull(a1) + c1 + _toStringWithNull(a2) + c2 + _toStringWithNull(a3) +
c3 + _toStringWithNull(a4) + c4 + _toStringWithNull(a5) + c5 + _toStringWithNull(a6) +
c6 + _toStringWithNull(a7) + c7 + _toStringWithNull(a8) + c8 + _toStringWithNull(a9) + c9;
default:
throw new Error(`Does not support more than 9 expressions`);
}
}
function _toStringWithNull(v: any): string {
return v != null ? v.toString() : '';
}
export const EMPTY_ARRAY: any[] = [];
export const EMPTY_MAP: {[key: string]: any} = {};

View File

@ -0,0 +1,593 @@
/**
* @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 {ViewEncapsulation} from '../metadata/view';
import {Renderer2, RendererType2} from '../render/api';
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement, listenToElementOutputs} from './element';
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
import {appendNgContent} from './ng_content';
import {callLifecycleHooksChildrenFirst, checkAndUpdateDirectiveDynamic, checkAndUpdateDirectiveInline, createDirectiveInstance, createPipeInstance, createProviderInstance} from './provider';
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
import {checkAndUpdateQuery, createQuery, queryDef} from './query';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, CheckType, ElementData, ElementDef, NodeData, NodeDef, NodeFlags, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList, asTextData} from './types';
import {checkBindingNoChanges, isComponentView, resolveViewDefinition, viewParentEl} from './util';
const NOOP = (): any => undefined;
export function viewDef(
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
updateRenderer?: ViewUpdateFn): ViewDefinition {
// clone nodes and set auto calculated values
if (nodes.length === 0) {
throw new Error(`Illegal State: Views without nodes are not allowed!`);
}
const reverseChildNodes: NodeDef[] = new Array(nodes.length);
let viewBindingCount = 0;
let viewDisposableCount = 0;
let viewNodeFlags = 0;
let viewRootNodeFlags = 0;
let viewMatchedQueries = 0;
let currentParent: NodeDef = null;
let currentElementHasPublicProviders = false;
let currentElementHasPrivateProviders = false;
let lastRenderRootNode: NodeDef = null;
for (let i = 0; i < nodes.length; i++) {
while (currentParent && i > currentParent.index + currentParent.childCount) {
const newParent = currentParent.parent;
if (newParent) {
newParent.childFlags |= currentParent.childFlags;
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
}
currentParent = newParent;
}
const node = nodes[i];
node.index = i;
node.parent = currentParent;
node.bindingIndex = viewBindingCount;
node.outputIndex = viewDisposableCount;
// renderParent needs to account for ng-container!
let currentRenderParent: NodeDef;
if (currentParent && currentParent.flags & NodeFlags.TypeElement &&
!currentParent.element.name) {
currentRenderParent = currentParent.renderParent;
} else {
currentRenderParent = currentParent;
}
node.renderParent = currentRenderParent;
if (node.element) {
const elDef = node.element;
elDef.publicProviders =
currentParent ? currentParent.element.publicProviders : Object.create(null);
elDef.allProviders = elDef.publicProviders;
// Note: We assume that all providers of an element are before any child element!
currentElementHasPublicProviders = false;
currentElementHasPrivateProviders = false;
}
validateNode(currentParent, node, nodes.length);
viewNodeFlags |= node.flags;
viewMatchedQueries |= node.matchedQueryIds;
if (node.element && node.element.template) {
viewMatchedQueries |= node.element.template.nodeMatchedQueries;
}
if (currentParent) {
currentParent.childFlags |= node.flags;
currentParent.directChildFlags |= node.flags;
currentParent.childMatchedQueries |= node.matchedQueryIds;
if (node.element && node.element.template) {
currentParent.childMatchedQueries |= node.element.template.nodeMatchedQueries;
}
} else {
viewRootNodeFlags |= node.flags;
}
viewBindingCount += node.bindings.length;
viewDisposableCount += node.outputs.length;
if (!currentRenderParent && (node.flags & NodeFlags.CatRenderNode)) {
lastRenderRootNode = node;
}
if (node.flags & NodeFlags.CatProvider) {
if (!currentElementHasPublicProviders) {
currentElementHasPublicProviders = true;
// Use protoypical inheritance to not get O(n^2) complexity...
currentParent.element.publicProviders =
Object.create(currentParent.element.publicProviders);
currentParent.element.allProviders = currentParent.element.publicProviders;
}
const isPrivateService = (node.flags & NodeFlags.PrivateProvider) !== 0;
const isComponent = (node.flags & NodeFlags.Component) !== 0;
if (!isPrivateService || isComponent) {
currentParent.element.publicProviders[node.provider.tokenKey] = node;
} else {
if (!currentElementHasPrivateProviders) {
currentElementHasPrivateProviders = true;
// Use protoypical inheritance to not get O(n^2) complexity...
currentParent.element.allProviders = Object.create(currentParent.element.publicProviders);
}
currentParent.element.allProviders[node.provider.tokenKey] = node;
}
if (isComponent) {
currentParent.element.componentProvider = node;
}
}
if (node.childCount) {
currentParent = node;
}
}
while (currentParent) {
const newParent = currentParent.parent;
if (newParent) {
newParent.childFlags |= currentParent.childFlags;
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
}
currentParent = newParent;
}
const handleEvent: ViewHandleEventFn = (view, nodeIndex, eventName, event) =>
nodes[nodeIndex].element.handleEvent(view, eventName, event);
return {
nodeFlags: viewNodeFlags,
rootNodeFlags: viewRootNodeFlags,
nodeMatchedQueries: viewMatchedQueries, flags,
nodes: nodes,
updateDirectives: updateDirectives || NOOP,
updateRenderer: updateRenderer || NOOP,
handleEvent: handleEvent || NOOP,
bindingCount: viewBindingCount,
outputCount: viewDisposableCount, lastRenderRootNode
};
}
function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) {
const template = node.element && node.element.template;
if (template) {
if (template.lastRenderRootNode &&
template.lastRenderRootNode.flags & NodeFlags.EmbeddedViews) {
throw new Error(
`Illegal State: Last root node of a template can't have embedded views, at index ${node.index}!`);
}
}
if (node.flags & NodeFlags.CatProvider) {
const parentFlags = parent ? parent.flags : null;
if ((parentFlags & NodeFlags.TypeElement) === 0) {
throw new Error(
`Illegal State: Provider/Directive nodes need to be children of elements or anchors, at index ${node.index}!`);
}
}
if (node.query) {
if (node.flags & NodeFlags.TypeContentQuery &&
(!parent || (parent.flags & NodeFlags.TypeDirective) === 0)) {
throw new Error(
`Illegal State: Content Query nodes need to be children of directives, at index ${node.index}!`);
}
if (node.flags & NodeFlags.TypeViewQuery && parent) {
throw new Error(
`Illegal State: View Query nodes have to be top level nodes, at index ${node.index}!`);
}
}
if (node.childCount) {
const parentEnd = parent ? parent.index + parent.childCount : nodeCount - 1;
if (node.index <= parentEnd && node.index + node.childCount > parentEnd) {
throw new Error(
`Illegal State: childCount of node leads outside of parent, at index ${node.index}!`);
}
}
}
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
// embedded views are seen as siblings to the anchor, so we need
// to get the parent of the anchor and use it as parentIndex.
const view =
createView(parent.root, parent.renderer, parent, anchorDef, anchorDef.element.template);
initView(view, parent.component, context);
createViewNodes(view);
return view;
}
export function createRootView(root: RootData, def: ViewDefinition, context?: any): ViewData {
const view = createView(root, root.renderer, null, null, def);
initView(view, context, context);
createViewNodes(view);
return view;
}
function createView(
root: RootData, renderer: Renderer2, parent: ViewData, parentNodeDef: NodeDef,
def: ViewDefinition): ViewData {
const nodes: NodeData[] = new Array(def.nodes.length);
const disposables = def.outputCount ? new Array(def.outputCount) : undefined;
const view: ViewData = {
def,
parent,
viewContainerParent: undefined, parentNodeDef,
context: undefined,
component: undefined, nodes,
state: ViewState.FirstCheck | ViewState.ChecksEnabled, root, renderer,
oldValues: new Array(def.bindingCount), disposables
};
return view;
}
function initView(view: ViewData, component: any, context: any) {
view.component = component;
view.context = context;
}
function createViewNodes(view: ViewData) {
let renderHost: any;
if (isComponentView(view)) {
const hostDef = view.parentNodeDef;
renderHost = asElementData(view.parent, hostDef.parent.index).renderElement;
}
const def = view.def;
const nodes = view.nodes;
for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i];
Services.setCurrentNode(view, i);
let nodeData: any;
switch (nodeDef.flags & NodeFlags.Types) {
case NodeFlags.TypeElement:
const el = createElement(view, renderHost, nodeDef) as any;
let componentView: ViewData;
if (nodeDef.flags & NodeFlags.ComponentView) {
const compViewDef = resolveViewDefinition(nodeDef.element.componentView);
const rendererType = nodeDef.element.componentRendererType;
let compRenderer: Renderer2;
if (!rendererType) {
compRenderer = view.root.renderer;
} else {
compRenderer = view.root.rendererFactory.createRenderer(el, rendererType);
}
componentView = createView(
view.root, compRenderer, view, nodeDef.element.componentProvider, compViewDef);
}
listenToElementOutputs(view, componentView, nodeDef, el);
nodeData = <ElementData>{
renderElement: el,
componentView,
embeddedViews: (nodeDef.flags & NodeFlags.EmbeddedViews) ? [] : undefined,
projectedViews: undefined
};
break;
case NodeFlags.TypeText:
nodeData = createText(view, renderHost, nodeDef) as any;
break;
case NodeFlags.TypeClassProvider:
case NodeFlags.TypeFactoryProvider:
case NodeFlags.TypeUseExistingProvider:
case NodeFlags.TypeValueProvider: {
const instance = createProviderInstance(view, nodeDef);
nodeData = <ProviderData>{instance};
break;
}
case NodeFlags.TypePipe: {
const instance = createPipeInstance(view, nodeDef);
nodeData = <ProviderData>{instance};
break;
}
case NodeFlags.TypeDirective: {
const instance = createDirectiveInstance(view, nodeDef);
nodeData = <ProviderData>{instance};
if (nodeDef.flags & NodeFlags.Component) {
const compView = asElementData(view, nodeDef.parent.index).componentView;
initView(compView, instance, instance);
}
break;
}
case NodeFlags.TypePureArray:
case NodeFlags.TypePureObject:
case NodeFlags.TypePurePipe:
nodeData = createPureExpression(view, nodeDef) as any;
break;
case NodeFlags.TypeContentQuery:
case NodeFlags.TypeViewQuery:
nodeData = createQuery() as any;
break;
case NodeFlags.TypeNgContent:
appendNgContent(view, renderHost, nodeDef);
// no runtime data needed for NgContent...
nodeData = undefined;
break;
}
nodes[i] = nodeData;
}
// Create the ViewData.nodes of component views after we created everything else,
// so that e.g. ng-content works
execComponentViewsAction(view, ViewAction.CreateViewNodes);
// fill static content and view queries
execQueriesAction(
view, NodeFlags.TypeContentQuery | NodeFlags.TypeViewQuery, NodeFlags.StaticQuery,
CheckType.CheckAndUpdate);
}
export function checkNoChangesView(view: ViewData) {
Services.updateDirectives(view, CheckType.CheckNoChanges);
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
execQueriesAction(
view, NodeFlags.TypeContentQuery, NodeFlags.DynamicQuery, CheckType.CheckNoChanges);
Services.updateRenderer(view, CheckType.CheckNoChanges);
execComponentViewsAction(view, ViewAction.CheckNoChanges);
execQueriesAction(
view, NodeFlags.TypeViewQuery, NodeFlags.DynamicQuery, CheckType.CheckNoChanges);
}
export function checkAndUpdateView(view: ViewData) {
Services.updateDirectives(view, CheckType.CheckAndUpdate);
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
view, NodeFlags.TypeContentQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);
callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterContentChecked |
(view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0));
Services.updateRenderer(view, CheckType.CheckAndUpdate);
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
view, NodeFlags.TypeViewQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);
callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterViewChecked |
(view.state & ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0));
if (view.def.flags & ViewFlags.OnPush) {
view.state &= ~ViewState.ChecksEnabled;
}
view.state &= ~ViewState.FirstCheck;
}
export function checkAndUpdateNode(
view: ViewData, nodeDef: NodeDef, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): boolean {
if (argStyle === ArgumentType.Inline) {
return checkAndUpdateNodeInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
} else {
return checkAndUpdateNodeDynamic(view, nodeDef, v0);
}
}
function checkAndUpdateNodeInline(
view: ViewData, nodeDef: NodeDef, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
v6?: any, v7?: any, v8?: any, v9?: any): boolean {
let changed = false;
switch (nodeDef.flags & NodeFlags.Types) {
case NodeFlags.TypeElement:
changed = checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
break;
case NodeFlags.TypeText:
changed = checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
break;
case NodeFlags.TypeDirective:
changed =
checkAndUpdateDirectiveInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
break;
case NodeFlags.TypePureArray:
case NodeFlags.TypePureObject:
case NodeFlags.TypePurePipe:
changed =
checkAndUpdatePureExpressionInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
break;
}
return changed;
}
function checkAndUpdateNodeDynamic(view: ViewData, nodeDef: NodeDef, values: any[]): boolean {
let changed = false;
switch (nodeDef.flags & NodeFlags.Types) {
case NodeFlags.TypeElement:
changed = checkAndUpdateElementDynamic(view, nodeDef, values);
break;
case NodeFlags.TypeText:
changed = checkAndUpdateTextDynamic(view, nodeDef, values);
break;
case NodeFlags.TypeDirective:
changed = checkAndUpdateDirectiveDynamic(view, nodeDef, values);
break;
case NodeFlags.TypePureArray:
case NodeFlags.TypePureObject:
case NodeFlags.TypePurePipe:
changed = checkAndUpdatePureExpressionDynamic(view, nodeDef, values);
break;
}
if (changed) {
// Update oldValues after all bindings have been updated,
// as a setter for a property might update other properties.
const bindLen = nodeDef.bindings.length;
const bindingStart = nodeDef.bindingIndex;
const oldValues = view.oldValues;
for (let i = 0; i < bindLen; i++) {
oldValues[bindingStart + i] = values[i];
}
}
return changed;
}
export function checkNoChangesNode(
view: ViewData, nodeDef: NodeDef, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any): any {
if (argStyle === ArgumentType.Inline) {
checkNoChangesNodeInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
} else {
checkNoChangesNodeDynamic(view, nodeDef, v0);
}
// Returning false is ok here as we would have thrown in case of a change.
return false;
}
function checkNoChangesNodeInline(
view: ViewData, nodeDef: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any): void {
const bindLen = nodeDef.bindings.length;
if (bindLen > 0) checkBindingNoChanges(view, nodeDef, 0, v0);
if (bindLen > 1) checkBindingNoChanges(view, nodeDef, 1, v1);
if (bindLen > 2) checkBindingNoChanges(view, nodeDef, 2, v2);
if (bindLen > 3) checkBindingNoChanges(view, nodeDef, 3, v3);
if (bindLen > 4) checkBindingNoChanges(view, nodeDef, 4, v4);
if (bindLen > 5) checkBindingNoChanges(view, nodeDef, 5, v5);
if (bindLen > 6) checkBindingNoChanges(view, nodeDef, 6, v6);
if (bindLen > 7) checkBindingNoChanges(view, nodeDef, 7, v7);
if (bindLen > 8) checkBindingNoChanges(view, nodeDef, 8, v8);
if (bindLen > 9) checkBindingNoChanges(view, nodeDef, 9, v9);
}
function checkNoChangesNodeDynamic(view: ViewData, nodeDef: NodeDef, values: any[]): void {
for (let i = 0; i < values.length; i++) {
checkBindingNoChanges(view, nodeDef, i, values[i]);
}
}
function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
const queryList = asQueryList(view, nodeDef.index);
if (queryList.dirty) {
throw expressionChangedAfterItHasBeenCheckedError(
Services.createDebugContext(view, nodeDef.index), `Query ${nodeDef.query.id} not dirty`,
`Query ${nodeDef.query.id} dirty`, (view.state & ViewState.FirstCheck) !== 0);
}
}
export function destroyView(view: ViewData) {
if (view.state & ViewState.Destroyed) {
return;
}
execEmbeddedViewsAction(view, ViewAction.Destroy);
execComponentViewsAction(view, ViewAction.Destroy);
callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy);
if (view.disposables) {
for (let i = 0; i < view.disposables.length; i++) {
view.disposables[i]();
}
}
if (view.renderer.destroyNode) {
destroyViewNodes(view);
}
if (isComponentView(view)) {
view.renderer.destroy();
}
view.state |= ViewState.Destroyed;
}
function destroyViewNodes(view: ViewData) {
const len = view.def.nodes.length;
for (let i = 0; i < len; i++) {
const def = view.def.nodes[i];
if (def.flags & NodeFlags.TypeElement) {
view.renderer.destroyNode(asElementData(view, i).renderElement);
} else if (def.flags & NodeFlags.TypeText) {
view.renderer.destroyNode(asTextData(view, i).renderText);
}
}
}
enum ViewAction {
CreateViewNodes,
CheckNoChanges,
CheckAndUpdate,
Destroy
}
function execComponentViewsAction(view: ViewData, action: ViewAction) {
const def = view.def;
if (!(def.nodeFlags & NodeFlags.ComponentView)) {
return;
}
for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i];
if (nodeDef.flags & NodeFlags.ComponentView) {
// a leaf
callViewAction(asElementData(view, i).componentView, action);
} else if ((nodeDef.childFlags & NodeFlags.ComponentView) === 0) {
// a parent with leafs
// no child is a component,
// then skip the children
i += nodeDef.childCount;
}
}
}
function execEmbeddedViewsAction(view: ViewData, action: ViewAction) {
const def = view.def;
if (!(def.nodeFlags & NodeFlags.EmbeddedViews)) {
return;
}
for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i];
if (nodeDef.flags & NodeFlags.EmbeddedViews) {
// a leaf
const embeddedViews = asElementData(view, i).embeddedViews;
if (embeddedViews) {
for (let k = 0; k < embeddedViews.length; k++) {
callViewAction(embeddedViews[k], action);
}
}
} else if ((nodeDef.childFlags & NodeFlags.EmbeddedViews) === 0) {
// a parent with leafs
// no child is a component,
// then skip the children
i += nodeDef.childCount;
}
}
}
function callViewAction(view: ViewData, action: ViewAction) {
const viewState = view.state;
switch (action) {
case ViewAction.CheckNoChanges:
if ((viewState & ViewState.ChecksEnabled) &&
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
checkNoChangesView(view);
}
break;
case ViewAction.CheckAndUpdate:
if ((viewState & ViewState.ChecksEnabled) &&
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
checkAndUpdateView(view);
}
break;
case ViewAction.Destroy:
destroyView(view);
break;
case ViewAction.CreateViewNodes:
createViewNodes(view);
break;
}
}
function execQueriesAction(
view: ViewData, queryFlags: NodeFlags, staticDynamicQueryFlag: NodeFlags,
checkType: CheckType) {
if (!(view.def.nodeFlags & queryFlags) || !(view.def.nodeFlags & staticDynamicQueryFlag)) {
return;
}
const nodeCount = view.def.nodes.length;
for (let i = 0; i < nodeCount; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.flags & queryFlags) && (nodeDef.flags & staticDynamicQueryFlag)) {
Services.setCurrentNode(view, nodeDef.index);
switch (checkType) {
case CheckType.CheckAndUpdate:
checkAndUpdateQuery(view, nodeDef);
break;
case CheckType.CheckNoChanges:
checkNoChangesQuery(view, nodeDef);
break;
}
}
if (!(nodeDef.childFlags & queryFlags) || !(nodeDef.childFlags & staticDynamicQueryFlag)) {
// no child has a matching query
// then skip the children
i += nodeDef.childCount;
}
}
}

View File

@ -0,0 +1,112 @@
/**
* @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 {ElementData, NodeData, NodeDef, NodeFlags, Services, ViewData, asElementData, asProviderData, asTextData} from './types';
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, rootRenderNodes, visitProjectedRenderNodes, visitRootRenderNodes} from './util';
export function attachEmbeddedView(
parentView: ViewData, elementData: ElementData, viewIndex: number, view: ViewData) {
let embeddedViews = elementData.embeddedViews;
if (viewIndex == null) {
viewIndex = embeddedViews.length;
}
view.viewContainerParent = parentView;
addToArray(embeddedViews, viewIndex, view);
const dvcElementData = declaredViewContainer(view);
if (dvcElementData && dvcElementData !== elementData) {
let projectedViews = dvcElementData.projectedViews;
if (!projectedViews) {
projectedViews = dvcElementData.projectedViews = [];
}
projectedViews.push(view);
}
Services.dirtyParentQueries(view);
const prevView = viewIndex > 0 ? embeddedViews[viewIndex - 1] : null;
renderAttachEmbeddedView(elementData, prevView, view);
}
export function detachEmbeddedView(elementData: ElementData, viewIndex: number): ViewData {
const embeddedViews = elementData.embeddedViews;
if (viewIndex == null || viewIndex >= embeddedViews.length) {
viewIndex = embeddedViews.length - 1;
}
if (viewIndex < 0) {
return null;
}
const view = embeddedViews[viewIndex];
view.viewContainerParent = undefined;
removeFromArray(embeddedViews, viewIndex);
const dvcElementData = declaredViewContainer(view);
if (dvcElementData && dvcElementData !== elementData) {
const projectedViews = dvcElementData.projectedViews;
removeFromArray(projectedViews, projectedViews.indexOf(view));
}
Services.dirtyParentQueries(view);
renderDetachView(view);
return view;
}
export function moveEmbeddedView(
elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData {
const embeddedViews = elementData.embeddedViews;
const view = embeddedViews[oldViewIndex];
removeFromArray(embeddedViews, oldViewIndex);
if (newViewIndex == null) {
newViewIndex = embeddedViews.length;
}
addToArray(embeddedViews, newViewIndex, view);
// Note: Don't need to change projectedViews as the order in there
// as always invalid...
Services.dirtyParentQueries(view);
renderDetachView(view);
const prevView = newViewIndex > 0 ? embeddedViews[newViewIndex - 1] : null;
renderAttachEmbeddedView(elementData, prevView, view);
return view;
}
function renderAttachEmbeddedView(elementData: ElementData, prevView: ViewData, view: ViewData) {
const prevRenderNode =
prevView ? renderNode(prevView, prevView.def.lastRenderRootNode) : elementData.renderElement;
const parentNode = view.renderer.parentNode(prevRenderNode);
const nextSibling = view.renderer.nextSibling(prevRenderNode);
// Note: We can't check if `nextSibling` is present, as on WebWorkers it will always be!
// However, browsers automatically do `appendChild` when there is no `nextSibling`.
visitRootRenderNodes(view, RenderNodeAction.InsertBefore, parentNode, nextSibling, undefined);
}
export function renderDetachView(view: ViewData) {
visitRootRenderNodes(view, RenderNodeAction.RemoveChild, null, null, undefined);
}
function addToArray(arr: any[], index: number, value: any) {
// perf: array.push is faster than array.splice!
if (index >= arr.length) {
arr.push(value);
} else {
arr.splice(index, 0, value);
}
}
function removeFromArray(arr: any[], index: number) {
// perf: array.pop is faster than array.splice!
if (index >= arr.length - 1) {
arr.pop();
} else {
arr.splice(index, 1);
}
}

10
packages/core/src/zone.ts Normal file
View File

@ -0,0 +1,10 @@
/**
* @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
*/
// Public API for Zone
export {NgZone} from './zone/ng_zone';

View File

@ -0,0 +1,283 @@
/**
* @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 {EventEmitter} from '../event_emitter';
/**
* An injectable service for executing work inside or outside of the Angular zone.
*
* The most common use of this service is to optimize performance when starting a work consisting of
* one or more asynchronous tasks that don't require UI updates or error handling to be handled by
* Angular. Such tasks can be kicked off via {@link runOutsideAngular} and if needed, these tasks
* can reenter the Angular zone via {@link run}.
*
* <!-- TODO: add/fix links to:
* - docs explaining zones and the use of zones in Angular and change-detection
* - link to runOutsideAngular/run (throughout this file!)
* -->
*
* ### Example
*
* ```
* import {Component, NgZone} from '@angular/core';
* import {NgIf} from '@angular/common';
*
* @Component({
* selector: 'ng-zone-demo'.
* template: `
* <h2>Demo: NgZone</h2>
*
* <p>Progress: {{progress}}%</p>
* <p *ngIf="progress >= 100">Done processing {{label}} of Angular zone!</p>
*
* <button (click)="processWithinAngularZone()">Process within Angular zone</button>
* <button (click)="processOutsideOfAngularZone()">Process outside of Angular zone</button>
* `,
* })
* export class NgZoneDemo {
* progress: number = 0;
* label: string;
*
* constructor(private _ngZone: NgZone) {}
*
* // Loop inside the Angular zone
* // so the UI DOES refresh after each setTimeout cycle
* processWithinAngularZone() {
* this.label = 'inside';
* this.progress = 0;
* this._increaseProgress(() => console.log('Inside Done!'));
* }
*
* // Loop outside of the Angular zone
* // so the UI DOES NOT refresh after each setTimeout cycle
* processOutsideOfAngularZone() {
* this.label = 'outside';
* this.progress = 0;
* this._ngZone.runOutsideAngular(() => {
* this._increaseProgress(() => {
* // reenter the Angular zone and display done
* this._ngZone.run(() => {console.log('Outside Done!') });
* }}));
* }
*
* _increaseProgress(doneCallback: () => void) {
* this.progress += 1;
* console.log(`Current progress: ${this.progress}%`);
*
* if (this.progress < 100) {
* window.setTimeout(() => this._increaseProgress(doneCallback)), 10)
* } else {
* doneCallback();
* }
* }
* }
* ```
*
* @experimental
*/
export class NgZone {
private outer: Zone;
private inner: Zone;
private _hasPendingMicrotasks: boolean = false;
private _hasPendingMacrotasks: boolean = false;
private _isStable = true;
private _nesting: number = 0;
private _onUnstable: EventEmitter<any> = new EventEmitter(false);
private _onMicrotaskEmpty: EventEmitter<any> = new EventEmitter(false);
private _onStable: EventEmitter<any> = new EventEmitter(false);
private _onErrorEvents: EventEmitter<any> = new EventEmitter(false);
constructor({enableLongStackTrace = false}) {
if (typeof Zone == 'undefined') {
throw new Error('Angular requires Zone.js prolyfill.');
}
Zone.assertZonePatched();
this.outer = this.inner = Zone.current;
if ((Zone as any)['wtfZoneSpec']) {
this.inner = this.inner.fork((Zone as any)['wtfZoneSpec']);
}
if (enableLongStackTrace && (Zone as any)['longStackTraceZoneSpec']) {
this.inner = this.inner.fork((Zone as any)['longStackTraceZoneSpec']);
}
this.forkInnerZoneWithAngularBehavior();
}
static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; }
static assertInAngularZone(): void {
if (!NgZone.isInAngularZone()) {
throw new Error('Expected to be in Angular Zone, but it is not!');
}
}
static assertNotInAngularZone(): void {
if (NgZone.isInAngularZone()) {
throw new Error('Expected to not be in Angular Zone, but it is!');
}
}
/**
* Executes the `fn` function synchronously within the Angular zone and returns value returned by
* the function.
*
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
* outside of the Angular zone (typically started via {@link runOutsideAngular}).
*
* Any future tasks or microtasks scheduled from within this function will continue executing from
* within the Angular zone.
*
* If a synchronous error happens it will be rethrown and not reported via `onError`.
*/
run(fn: () => any): any { return this.inner.run(fn); }
/**
* Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not
* rethrown.
*/
runGuarded(fn: () => any): any { return this.inner.runGuarded(fn); }
/**
* Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
* the function.
*
* Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that
* doesn't trigger Angular change-detection or is subject to Angular's error handling.
*
* Any future tasks or microtasks scheduled from within this function will continue executing from
* outside of the Angular zone.
*
* Use {@link run} to reenter the Angular zone and do work that updates the application model.
*/
runOutsideAngular(fn: () => any): any { return this.outer.run(fn); }
/**
* Notifies when code enters Angular Zone. This gets fired first on VM Turn.
*/
get onUnstable(): EventEmitter<any> { return this._onUnstable; }
/**
* Notifies when there is no more microtasks enqueue in the current VM Turn.
* This is a hint for Angular to do change detection, which may enqueue more microtasks.
* For this reason this event can fire multiple times per VM Turn.
*/
get onMicrotaskEmpty(): EventEmitter<any> { return this._onMicrotaskEmpty; }
/**
* Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which
* implies we are about to relinquish VM turn.
* This event gets called just once.
*/
get onStable(): EventEmitter<any> { return this._onStable; }
/**
* Notify that an error has been delivered.
*/
get onError(): EventEmitter<any> { return this._onErrorEvents; }
/**
* Whether there are no outstanding microtasks or macrotasks.
*/
get isStable(): boolean { return this._isStable; }
get hasPendingMicrotasks(): boolean { return this._hasPendingMicrotasks; }
get hasPendingMacrotasks(): boolean { return this._hasPendingMacrotasks; }
private checkStable() {
if (this._nesting == 0 && !this._hasPendingMicrotasks && !this._isStable) {
try {
this._nesting++;
this._onMicrotaskEmpty.emit(null);
} finally {
this._nesting--;
if (!this._hasPendingMicrotasks) {
try {
this.runOutsideAngular(() => this._onStable.emit(null));
} finally {
this._isStable = true;
}
}
}
}
}
private forkInnerZoneWithAngularBehavior() {
this.inner = this.inner.fork({
name: 'angular',
properties: <any>{'isAngularZone': true},
onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task,
applyThis: any, applyArgs: any): any => {
try {
this.onEnter();
return delegate.invokeTask(target, task, applyThis, applyArgs);
} finally {
this.onLeave();
}
},
onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function,
applyThis: any, applyArgs: any[], source: string): any => {
try {
this.onEnter();
return delegate.invoke(target, callback, applyThis, applyArgs, source);
} finally {
this.onLeave();
}
},
onHasTask:
(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
delegate.hasTask(target, hasTaskState);
if (current === target) {
// We are only interested in hasTask events which originate from our zone
// (A child hasTask event is not interesting to us)
if (hasTaskState.change == 'microTask') {
this.setHasMicrotask(hasTaskState.microTask);
} else if (hasTaskState.change == 'macroTask') {
this.setHasMacrotask(hasTaskState.macroTask);
}
}
},
onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
delegate.handleError(target, error);
this.triggerError(error);
return false;
}
});
}
private onEnter() {
this._nesting++;
if (this._isStable) {
this._isStable = false;
this._onUnstable.emit(null);
}
}
private onLeave() {
this._nesting--;
this.checkStable();
}
private setHasMicrotask(hasMicrotasks: boolean) {
this._hasPendingMicrotasks = hasMicrotasks;
this.checkStable();
}
private setHasMacrotask(hasMacrotasks: boolean) { this._hasPendingMacrotasks = hasMacrotasks; }
private triggerError(error: any) { this._onErrorEvents.emit(error); }
}