2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2017-05-18 16:46:51 -04:00
|
|
|
import {Component, Directive, HostBinding, HostListener, Input, Output, Query, Type, resolveForwardRef, ɵstringify as stringify} from '@angular/core';
|
|
|
|
|
|
|
|
import {CompileReflector} from './compile_reflector';
|
2016-12-15 12:12:40 -05:00
|
|
|
import {CompilerInjectable} from './injectable';
|
2016-07-11 19:01:49 -04:00
|
|
|
import {splitAtColon} from './util';
|
2014-11-11 20:33:47 -05:00
|
|
|
|
2017-03-01 17:10:59 -05:00
|
|
|
|
2017-05-18 16:46:51 -04:00
|
|
|
|
2015-09-19 21:39:35 -04:00
|
|
|
/*
|
2016-09-12 22:14:17 -04:00
|
|
|
* Resolve a `Type` for {@link Directive}.
|
2015-07-07 02:15:58 -04:00
|
|
|
*
|
|
|
|
* This interface can be overridden by the application developer to create custom behavior.
|
|
|
|
*
|
|
|
|
* See {@link Compiler}
|
|
|
|
*/
|
2016-12-15 12:12:40 -05:00
|
|
|
@CompilerInjectable()
|
2015-05-11 20:59:39 -04:00
|
|
|
export class DirectiveResolver {
|
2017-05-18 16:46:51 -04:00
|
|
|
constructor(private _reflector: CompileReflector) {}
|
2016-03-24 16:32:47 -04:00
|
|
|
|
2016-11-10 17:07:30 -05:00
|
|
|
isDirective(type: Type<any>) {
|
|
|
|
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
|
|
|
return typeMetadata && typeMetadata.some(isDirectiveMetadata);
|
|
|
|
}
|
|
|
|
|
2015-07-07 02:15:58 -04:00
|
|
|
/**
|
2016-09-12 22:14:17 -04:00
|
|
|
* Return {@link Directive} for a given `Type`.
|
2015-07-07 02:15:58 -04:00
|
|
|
*/
|
2017-03-24 12:59:58 -04:00
|
|
|
resolve(type: Type<any>): Directive;
|
|
|
|
resolve(type: Type<any>, throwIfNotFound: true): Directive;
|
|
|
|
resolve(type: Type<any>, throwIfNotFound: boolean): Directive|null;
|
|
|
|
resolve(type: Type<any>, throwIfNotFound = true): Directive|null {
|
2016-09-27 18:41:37 -04:00
|
|
|
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
|
|
|
if (typeMetadata) {
|
2017-03-01 17:10:59 -05:00
|
|
|
const metadata = findLast(typeMetadata, isDirectiveMetadata);
|
2016-09-27 18:41:37 -04:00
|
|
|
if (metadata) {
|
|
|
|
const propertyMetadata = this._reflector.propMetadata(type);
|
2015-11-04 04:08:51 -05:00
|
|
|
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
|
|
|
}
|
2016-09-27 18:41:37 -04:00
|
|
|
|
2016-07-18 06:50:31 -04:00
|
|
|
if (throwIfNotFound) {
|
2016-08-25 03:50:16 -04:00
|
|
|
throw new Error(`No Directive annotation found on ${stringify(type)}`);
|
2016-07-18 06:50:31 -04:00
|
|
|
}
|
2016-09-27 18:41:37 -04:00
|
|
|
|
2016-07-18 06:50:31 -04:00
|
|
|
return null;
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
2015-09-03 18:10:48 -04:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
private _mergeWithPropertyMetadata(
|
2016-09-12 22:14:17 -04:00
|
|
|
dm: Directive, propertyMetadata: {[key: string]: any[]},
|
|
|
|
directiveType: Type<any>): Directive {
|
2016-09-27 18:41:37 -04:00
|
|
|
const inputs: string[] = [];
|
|
|
|
const outputs: string[] = [];
|
|
|
|
const host: {[key: string]: string} = {};
|
|
|
|
const queries: {[key: string]: any} = {};
|
|
|
|
|
|
|
|
Object.keys(propertyMetadata).forEach((propName: string) => {
|
2017-03-01 17:10:59 -05:00
|
|
|
const input = findLast(propertyMetadata[propName], (a) => a instanceof Input);
|
2016-11-18 18:17:44 -05:00
|
|
|
if (input) {
|
|
|
|
if (input.bindingPropertyName) {
|
|
|
|
inputs.push(`${propName}: ${input.bindingPropertyName}`);
|
|
|
|
} else {
|
|
|
|
inputs.push(propName);
|
|
|
|
}
|
|
|
|
}
|
2017-03-01 17:10:59 -05:00
|
|
|
const output = findLast(propertyMetadata[propName], (a) => a instanceof Output);
|
2016-11-18 18:17:44 -05:00
|
|
|
if (output) {
|
|
|
|
if (output.bindingPropertyName) {
|
|
|
|
outputs.push(`${propName}: ${output.bindingPropertyName}`);
|
|
|
|
} else {
|
|
|
|
outputs.push(propName);
|
|
|
|
}
|
|
|
|
}
|
2016-12-14 17:31:57 -05:00
|
|
|
const hostBindings = propertyMetadata[propName].filter(a => a && a instanceof HostBinding);
|
|
|
|
hostBindings.forEach(hostBinding => {
|
2016-11-18 18:17:44 -05:00
|
|
|
if (hostBinding.hostPropertyName) {
|
|
|
|
const startWith = hostBinding.hostPropertyName[0];
|
|
|
|
if (startWith === '(') {
|
|
|
|
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
|
|
|
} else if (startWith === '[') {
|
|
|
|
throw new Error(
|
|
|
|
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
2015-09-04 17:07:16 -04:00
|
|
|
}
|
2016-11-18 18:17:44 -05:00
|
|
|
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
|
|
|
} else {
|
|
|
|
host[`[${propName}]`] = propName;
|
2015-09-19 21:39:35 -04:00
|
|
|
}
|
2016-12-14 17:31:57 -05:00
|
|
|
});
|
2016-12-14 18:10:43 -05:00
|
|
|
const hostListeners = propertyMetadata[propName].filter(a => a && a instanceof HostListener);
|
2016-12-14 17:31:57 -05:00
|
|
|
hostListeners.forEach(hostListener => {
|
2016-11-18 18:17:44 -05:00
|
|
|
const args = hostListener.args || [];
|
|
|
|
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
2016-12-14 17:31:57 -05:00
|
|
|
});
|
2017-03-01 17:10:59 -05:00
|
|
|
const query = findLast(propertyMetadata[propName], (a) => a instanceof Query);
|
2016-11-18 18:17:44 -05:00
|
|
|
if (query) {
|
|
|
|
queries[propName] = query;
|
|
|
|
}
|
2015-09-03 18:10:48 -04:00
|
|
|
});
|
2015-11-04 04:08:51 -05:00
|
|
|
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
2015-09-03 18:10:48 -04:00
|
|
|
}
|
|
|
|
|
2017-03-24 12:59:58 -04:00
|
|
|
private _extractPublicName(def: string) { return splitAtColon(def, [null !, def])[1].trim(); }
|
2016-07-11 19:01:49 -04:00
|
|
|
|
2016-11-18 18:17:44 -05:00
|
|
|
private _dedupeBindings(bindings: string[]): string[] {
|
|
|
|
const names = new Set<string>();
|
|
|
|
const reversedResult: string[] = [];
|
|
|
|
// go last to first to allow later entries to overwrite previous entries
|
|
|
|
for (let i = bindings.length - 1; i >= 0; i--) {
|
|
|
|
const binding = bindings[i];
|
|
|
|
const name = this._extractPublicName(binding);
|
|
|
|
if (!names.has(name)) {
|
|
|
|
names.add(name);
|
|
|
|
reversedResult.push(binding);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return reversedResult.reverse();
|
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
private _merge(
|
2016-09-27 18:41:37 -04:00
|
|
|
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
2016-09-12 22:14:17 -04:00
|
|
|
queries: {[key: string]: any}, directiveType: Type<any>): Directive {
|
2016-11-18 18:17:44 -05:00
|
|
|
const mergedInputs =
|
|
|
|
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
|
|
|
|
const mergedOutputs =
|
|
|
|
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
|
2017-03-24 17:45:33 -04:00
|
|
|
const mergedHost = directive.host ? {...directive.host, ...host} : host;
|
|
|
|
const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries;
|
2015-09-03 18:10:48 -04:00
|
|
|
|
2016-09-27 18:41:37 -04:00
|
|
|
if (directive instanceof Component) {
|
2016-09-12 22:14:17 -04:00
|
|
|
return new Component({
|
2016-09-27 18:41:37 -04:00
|
|
|
selector: directive.selector,
|
2015-09-30 23:59:23 -04:00
|
|
|
inputs: mergedInputs,
|
|
|
|
outputs: mergedOutputs,
|
2015-09-04 17:07:16 -04:00
|
|
|
host: mergedHost,
|
2016-09-27 18:41:37 -04:00
|
|
|
exportAs: directive.exportAs,
|
|
|
|
moduleId: directive.moduleId,
|
2015-09-17 21:45:49 -04:00
|
|
|
queries: mergedQueries,
|
2016-09-27 18:41:37 -04:00
|
|
|
changeDetection: directive.changeDetection,
|
|
|
|
providers: directive.providers,
|
|
|
|
viewProviders: directive.viewProviders,
|
|
|
|
entryComponents: directive.entryComponents,
|
|
|
|
template: directive.template,
|
|
|
|
templateUrl: directive.templateUrl,
|
|
|
|
styles: directive.styles,
|
|
|
|
styleUrls: directive.styleUrls,
|
|
|
|
encapsulation: directive.encapsulation,
|
|
|
|
animations: directive.animations,
|
2017-07-28 09:58:28 -04:00
|
|
|
interpolation: directive.interpolation,
|
|
|
|
preserveWhitespaces: directive.preserveWhitespaces,
|
2015-09-03 18:10:48 -04:00
|
|
|
});
|
|
|
|
} else {
|
2016-09-12 22:14:17 -04:00
|
|
|
return new Directive({
|
2016-09-27 18:41:37 -04:00
|
|
|
selector: directive.selector,
|
2015-09-30 23:59:23 -04:00
|
|
|
inputs: mergedInputs,
|
|
|
|
outputs: mergedOutputs,
|
2015-09-04 17:07:16 -04:00
|
|
|
host: mergedHost,
|
2016-09-27 18:41:37 -04:00
|
|
|
exportAs: directive.exportAs,
|
2015-10-11 01:11:13 -04:00
|
|
|
queries: mergedQueries,
|
2016-09-27 18:41:37 -04:00
|
|
|
providers: directive.providers
|
2015-09-03 18:10:48 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2014-11-11 20:33:47 -05:00
|
|
|
}
|
2016-09-27 18:41:37 -04:00
|
|
|
|
|
|
|
function isDirectiveMetadata(type: any): type is Directive {
|
|
|
|
return type instanceof Directive;
|
|
|
|
}
|
2017-03-01 17:10:59 -05:00
|
|
|
|
2017-03-24 12:59:58 -04:00
|
|
|
export function findLast<T>(arr: T[], condition: (value: T) => boolean): T|null {
|
2017-03-01 17:10:59 -05:00
|
|
|
for (let i = arr.length - 1; i >= 0; i--) {
|
|
|
|
if (condition(arr[i])) {
|
|
|
|
return arr[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|