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