2018-04-04 21:21:12 -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
*/
2019-04-30 20:24:00 -07:00
import { InjectorType } from '../di/interface/defs' ;
import { stringify } from '../util/stringify' ;
2018-04-04 21:21:12 -07:00
import { TNode } from './interfaces/node' ;
2019-12-10 16:23:56 -08:00
import { LView , TVIEW } from './interfaces/view' ;
import { INTERPOLATION_DELIMITER } from './util/misc_utils' ;
2018-04-04 21:21:12 -07:00
2019-04-30 20:24:00 -07:00
2018-04-04 21:21:12 -07:00
/** Called when directives inject each other (creating a circular dependency) */
export function throwCyclicDependencyError ( token : any ) : never {
throw new Error ( `Cannot instantiate cyclic dependency! ${ token } ` );
}
/** Called when there are multiple component selectors that match a given node */
export function throwMultipleComponentError ( tNode : TNode ) : never {
throw new Error ( `Multiple components match node with tagname ${ tNode . tagName } ` );
}
2019-04-30 20:24:00 -07:00
export function throwMixedMultiProviderError() {
throw new Error ( `Cannot mix multi providers and regular providers` );
}
export function throwInvalidProviderError (
ngModuleType? : InjectorType < any >, providers? : any [], provider? : any ) {
let ngModuleDetail = '' ;
if ( ngModuleType && providers ) {
const providerDetail = providers . map ( v => v == provider ? '?' + provider + '?' : '...' );
ngModuleDetail =
` - only instances of Provider and Type are allowed, got: [ ${ providerDetail . join ( ', ' ) } ]` ;
}
throw new Error (
`Invalid provider for the NgModule ' ${ stringify ( ngModuleType ) } '` + ngModuleDetail );
}
2019-12-10 16:23:56 -08:00
/** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
export function throwErrorIfNoChangesMode (
creationMode : boolean , oldValue : any , currValue : any , propName? : string ) : never | void {
const field = propName ? ` for ' ${ propName } '` : '' ;
let msg =
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value ${ field } : ' ${ oldValue } '. Current value: ' ${ currValue } '.` ;
if ( creationMode ) {
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?` ;
}
// TODO: include debug context, see `viewDebugError` function in
// `packages/core/src/view/errors.ts` for reference.
throw new Error ( msg );
}
function constructDetailsForInterpolation (
lView : LView , rootIndex : number , expressionIndex : number , meta : string , changedValue : any ) {
const [ propName , prefix , ... chunks ] = meta . split ( INTERPOLATION_DELIMITER );
let oldValue = prefix , newValue = prefix ;
for ( let i = 0 ; i < chunks . length ; i ++ ) {
const slotIdx = rootIndex + i ;
oldValue += ` ${ lView [ slotIdx ] }${ chunks [ i ] } ` ;
newValue += ` ${ slotIdx === expressionIndex ? changedValue : lView [ slotIdx ] }${ chunks [ i ] } ` ;
}
return { propName , oldValue , newValue };
}
/**
* Constructs an object that contains details for the ExpressionChangedAfterItHasBeenCheckedError:
* - property name (for property bindings or interpolations)
* - old and new values, enriched using information from metadata
*
* More information on the metadata storage format can be found in `storePropertyBindingMetadata`
* function description.
*/
export function getExpressionChangedErrorDetails (
lView : LView , bindingIndex : number , oldValue : any ,
newValue : any ) : { propName? : string , oldValue : any , newValue : any } {
const tData = lView [ TVIEW ]. data ;
const metadata = tData [ bindingIndex ];
if ( typeof metadata === 'string' ) {
// metadata for property interpolation
if ( metadata . indexOf ( INTERPOLATION_DELIMITER ) > - 1 ) {
return constructDetailsForInterpolation (
lView , bindingIndex , bindingIndex , metadata , newValue );
}
// metadata for property binding
return { propName : metadata , oldValue , newValue };
}
// metadata is not available for this expression, check if this expression is a part of the
// property interpolation by going from the current binding index left and look for a string that
// contains INTERPOLATION_DELIMITER, the layout in tView.data for this case will look like this:
// [..., 'id� Prefix � and � suffix', null, null, null, ...]
if ( metadata === null ) {
let idx = bindingIndex - 1 ;
while ( typeof tData [ idx ] !== 'string' && tData [ idx + 1 ] === null ) {
idx -- ;
}
const meta = tData [ idx ];
if ( typeof meta === 'string' ) {
const matches = meta . match ( new RegExp ( INTERPOLATION_DELIMITER , 'g' ));
// first interpolation delimiter separates property name from interpolation parts (in case of
// property interpolations), so we subtract one from total number of found delimiters
if ( matches && ( matches . length - 1 ) > bindingIndex - idx ) {
return constructDetailsForInterpolation ( lView , idx , bindingIndex , meta , newValue );
}
}
}
return { propName : undefined , oldValue , newValue };
}