mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 836530fdfd | |||
| ca81661c95 | |||
| 5bf5cac714 | |||
| 70861909a7 | |||
| f54cd057ad | |||
| 4ae88dc9be | |||
| c8ac2b6d1e | |||
| 8151dd3cb6 | |||
| 51ce1d6228 | |||
| f79b106dfc | |||
| 7504f24fe2 | |||
| 63084b6502 | |||
| 1200ae9c6e | |||
| 0dbccabdf9 | |||
| 1d2f7545c5 | |||
| bf16d7e414 | |||
| 6106fcf4c7 | |||
| 3e01740e43 | |||
| 968700c5f9 | |||
| f108800804 | |||
| c77976d9aa | |||
| 2c754081e1 | |||
| 1a2a6659a0 | |||
| 1067f8a439 | |||
| e94b474b96 | |||
| 34f002db7e | |||
| a0af416ca1 | |||
| 6dc61179ca | |||
| c09cace82b | |||
| 6840f93f37 | |||
| 3f5e3001f4 | |||
| e5e405ee6e | |||
| b263c00d36 | |||
| 3bd2fcd044 | |||
| 7feda88abb | |||
| f7265d34ac | |||
| 5264c18b53 | |||
| 5ab3598746 | |||
| f877b544a7 | |||
| f5edc3e9a8 | |||
| 9bf377a424 | |||
| 0eced157a9 | |||
| df4196e63f | |||
| 5dea0312d6 | |||
| 7b15e261ad | |||
| a59dcb52bd | |||
| 6cd5dfa966 | |||
| 809a96e65d | |||
| 2c6154fe14 | |||
| bdbb76f344 | |||
| 24d689730d | |||
| 6310ccc221 | |||
| faf5b21275 | |||
| 317ddb0d00 | |||
| b7f33034e8 | |||
| a79e202f33 | |||
| 92f4d0127f | |||
| 43cf37aa3e | |||
| 41db51bf91 | |||
| 1ae67024cb | |||
| b04e75a175 | |||
| d56cbc2fdf | |||
| 49cbecaeab | |||
| a6702e92aa | |||
| dadd751a48 | |||
| 48054443da | |||
| 84e0908b29 | |||
| 0f228885bf | |||
| af93b126dd | |||
| f7408579a4 | |||
| c00ab4bcea | |||
| f0a92e2097 | |||
| e35ac097be | |||
| 647f57407b | |||
| 2bb73d1d09 | |||
| ea48506f67 | |||
| d49aec75df | |||
| f050c5b19a | |||
| 565032c038 |
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- signals
|
||||
- '[0-9]+.[0-9]+.x'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
@@ -77,6 +77,8 @@ export enum ErrorCode {
|
||||
PARAM_MISSING_TOKEN = 2003,
|
||||
// (undocumented)
|
||||
PIPE_MISSING_NAME = 2002,
|
||||
QUERY_DEFINITION_ARG_NOT_LITERAL = 9003,
|
||||
QUERY_DEFINITION_ARITY_WRONG = 9002,
|
||||
SCHEMA_INVALID_ATTRIBUTE = 8002,
|
||||
SCHEMA_INVALID_ELEMENT = 8001,
|
||||
SKIP_HYDRATION_NOT_STATIC = 8108,
|
||||
|
||||
@@ -228,6 +228,7 @@ export interface Component extends Directive {
|
||||
moduleId?: string;
|
||||
preserveWhitespaces?: boolean;
|
||||
schemas?: SchemaMetadata[];
|
||||
signals?: boolean;
|
||||
standalone?: boolean;
|
||||
styles?: string | string[];
|
||||
styleUrl?: string;
|
||||
@@ -320,6 +321,13 @@ export type ContentChild = Query;
|
||||
// @public
|
||||
export const ContentChild: ContentChildDecorator;
|
||||
|
||||
// @public (undocumented)
|
||||
export function contentChild<T>(selector: ProviderToken<T> | string, opts?: {
|
||||
descendants?: boolean;
|
||||
read?: any;
|
||||
static?: boolean;
|
||||
}): Signal<T | undefined>;
|
||||
|
||||
// @public
|
||||
export interface ContentChildDecorator {
|
||||
(selector: ProviderToken<unknown> | Function | string, opts?: {
|
||||
@@ -341,6 +349,13 @@ export type ContentChildren = Query;
|
||||
// @public
|
||||
export const ContentChildren: ContentChildrenDecorator;
|
||||
|
||||
// @public (undocumented)
|
||||
export function contentChildren<T>(selector: ProviderToken<T> | string, opts?: {
|
||||
descendants?: boolean;
|
||||
read?: any;
|
||||
emitDistinctChangesOnly?: boolean;
|
||||
}): Signal<T[]>;
|
||||
|
||||
// @public
|
||||
export interface ContentChildrenDecorator {
|
||||
(selector: ProviderToken<unknown> | Function | string, opts?: {
|
||||
@@ -523,6 +538,7 @@ export interface Directive {
|
||||
[key: string]: any;
|
||||
};
|
||||
selector?: string;
|
||||
signals?: boolean;
|
||||
standalone?: boolean;
|
||||
}
|
||||
|
||||
@@ -862,6 +878,45 @@ export interface Input {
|
||||
// @public (undocumented)
|
||||
export const Input: InputDecorator;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input(): InputSignal<undefined, undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<T>(): InputSignal<T | undefined, T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<T>(initialValue: T & (string | number | boolean), opts?: PrimaryInputOptions<T, T> & {
|
||||
transform?: undefined;
|
||||
}): InputSignal<T, T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<ReadT, WriteT = ReadT>(initialValue: WriteT & (string | number | boolean), opts: PrimaryInputOptions<ReadT, WriteT>): InputSignal<ReadT, WriteT>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<T>(opts: InputOptions<T, T> & {
|
||||
required: true;
|
||||
transform?: undefined;
|
||||
}): InputSignal<T, T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<ReadT, WriteT = ReadT>(opts: InputOptions<ReadT, WriteT> & {
|
||||
required: true;
|
||||
}): InputSignal<ReadT, WriteT>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<T>(opts: InputOptions<T, T> & {
|
||||
initialValue: T;
|
||||
transform?: undefined;
|
||||
}): InputSignal<T, T>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<ReadT, WriteT = ReadT>(opts: InputOptions<ReadT, WriteT> & {
|
||||
initialValue: ReadT;
|
||||
}): InputSignal<ReadT, WriteT>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function input<ReadT, WriteT = ReadT>(opts: InputOptions<ReadT, WriteT>): InputSignal<ReadT | undefined, WriteT>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface InputDecorator {
|
||||
(arg?: string | Input): any;
|
||||
@@ -869,6 +924,11 @@ export interface InputDecorator {
|
||||
new (arg?: string | Input): any;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type InputSignal<ReadT, WriteT> = Signal<ReadT> & {
|
||||
[BRAND_WRITE_TYPE]: WriteT;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function isDevMode(): boolean;
|
||||
|
||||
@@ -988,6 +1048,9 @@ export enum MissingTranslationStrategy {
|
||||
Warning = 1
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ModelSignal<ReadT, WriteT> = InputSignal<ReadT, WriteT> & Pick<WritableSignal<WriteT>, 'set' | 'update'>;
|
||||
|
||||
// @public @deprecated
|
||||
export class ModuleWithComponentFactories<T> {
|
||||
constructor(ngModuleFactory: NgModuleFactory<T>, componentFactories: ComponentFactory<any>[]);
|
||||
@@ -1131,6 +1194,12 @@ export interface Output {
|
||||
// @public (undocumented)
|
||||
export const Output: OutputDecorator;
|
||||
|
||||
// @public (undocumented)
|
||||
export function output<T>(opts?: {
|
||||
alias?: string;
|
||||
isAsync?: boolean;
|
||||
}): EventEmitter<T>;
|
||||
|
||||
// @public
|
||||
export interface OutputDecorator {
|
||||
(alias?: string): any;
|
||||
@@ -1232,7 +1301,7 @@ export class QueryList<T> implements Iterable<T> {
|
||||
get changes(): Observable<any>;
|
||||
destroy(): void;
|
||||
// (undocumented)
|
||||
readonly dirty = true;
|
||||
readonly dirty: boolean;
|
||||
filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S): S[];
|
||||
// (undocumented)
|
||||
filter(predicate: (value: T, index: number, array: readonly T[]) => unknown): T[];
|
||||
@@ -1247,6 +1316,7 @@ export class QueryList<T> implements Iterable<T> {
|
||||
readonly length: number;
|
||||
map<U>(fn: (item: T, index: number, array: T[]) => U): U[];
|
||||
notifyOnChanges(): void;
|
||||
onDirty(cb: () => void): void;
|
||||
reduce<U>(fn: (prevValue: U, curValue: T, curIndex: number, array: T[]) => U, init: U): U;
|
||||
reset(resultsTree: Array<T | any[]>, identityAccessor?: (value: T) => unknown): void;
|
||||
setDirty(): void;
|
||||
@@ -1549,6 +1619,12 @@ export type ViewChild = Query;
|
||||
// @public
|
||||
export const ViewChild: ViewChildDecorator;
|
||||
|
||||
// @public (undocumented)
|
||||
export function viewChild<T>(selector: ProviderToken<T> | string, opts?: {
|
||||
read?: any;
|
||||
static?: boolean;
|
||||
}): Signal<T | undefined>;
|
||||
|
||||
// @public
|
||||
export interface ViewChildDecorator {
|
||||
(selector: ProviderToken<unknown> | Function | string, opts?: {
|
||||
@@ -1568,6 +1644,12 @@ export type ViewChildren = Query;
|
||||
// @public
|
||||
export const ViewChildren: ViewChildrenDecorator;
|
||||
|
||||
// @public (undocumented)
|
||||
export function viewChildren<T>(selector: ProviderToken<T> | string, opts?: {
|
||||
read?: any;
|
||||
emitDistinctChangesOnly?: boolean;
|
||||
}): Signal<T[]>;
|
||||
|
||||
// @public
|
||||
export interface ViewChildrenDecorator {
|
||||
(selector: ProviderToken<unknown> | Function | string, opts?: {
|
||||
|
||||
@@ -4,6 +4,43 @@
|
||||
|
||||
```ts
|
||||
|
||||
// @public
|
||||
export const COMPUTED_NODE: {
|
||||
value: any;
|
||||
dirty: boolean;
|
||||
error: null;
|
||||
equal: typeof defaultEquals;
|
||||
producerMustRecompute(node: ComputedNode<unknown>): boolean;
|
||||
producerRecomputeValue(node: ComputedNode<unknown>): void;
|
||||
version: number & {
|
||||
__brand: "Version";
|
||||
};
|
||||
producerNode: ReactiveNode[] | undefined;
|
||||
producerLastReadVersion: (number & {
|
||||
__brand: "Version";
|
||||
})[] | undefined;
|
||||
producerIndexOfThis: number[] | undefined;
|
||||
nextProducerIndex: number;
|
||||
liveConsumerNode: ReactiveNode[] | undefined;
|
||||
liveConsumerIndexOfThis: number[] | undefined;
|
||||
consumerAllowSignalWrites: boolean;
|
||||
consumerIsAlwaysLive: boolean;
|
||||
consumerMarkedDirty(node: unknown): void;
|
||||
consumerOnSignalRead(node: unknown): void;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface ComputedNode<T> extends ReactiveNode {
|
||||
computation: () => T;
|
||||
// (undocumented)
|
||||
equal: ValueEqualityFn<T>;
|
||||
error: unknown;
|
||||
value: T;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const COMPUTING: any;
|
||||
|
||||
// @public
|
||||
export function consumerAfterComputation(node: ReactiveNode | null, prevConsumer: ReactiveNode | null): void;
|
||||
|
||||
@@ -13,6 +50,9 @@ export function consumerBeforeComputation(node: ReactiveNode | null): ReactiveNo
|
||||
// @public
|
||||
export function consumerDestroy(node: ReactiveNode): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function consumerMarkDirty(node: ReactiveNode): void;
|
||||
|
||||
// @public
|
||||
export function createComputed<T>(computation: () => T): ComputedGetter<T>;
|
||||
|
||||
@@ -25,6 +65,9 @@ export function createWatch(fn: (onCleanup: WatchCleanupRegisterFn) => void, sch
|
||||
// @public
|
||||
export function defaultEquals<T>(a: T, b: T): boolean;
|
||||
|
||||
// @public
|
||||
export const ERRORED: any;
|
||||
|
||||
// @public (undocumented)
|
||||
export function getActiveConsumer(): ReactiveNode | null;
|
||||
|
||||
@@ -116,6 +159,9 @@ export function signalSetFn<T>(node: SignalNode<T>, newValue: T): void;
|
||||
// @public (undocumented)
|
||||
export function signalUpdateFn<T>(node: SignalNode<T>, updater: (value: T) => T): void;
|
||||
|
||||
// @public
|
||||
export const UNSET: any;
|
||||
|
||||
// @public
|
||||
export type ValueEqualityFn<T> = (a: T, b: T) => boolean;
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"ng-dev": "ts-node --esm --project .ng-dev/tsconfig.json --transpile-only node_modules/@angular/ng-dev/bundles/cli.mjs",
|
||||
"build": "ts-node --esm --project scripts/tsconfig.json scripts/build/build-packages-dist.mts",
|
||||
"test": "bazelisk test",
|
||||
"test:ci": "bazelisk test -- //... -//devtools/... -//aio/... && bazelisk test --//packages/compiler:use_template_pipeline //packages/compiler-cli/test/compliance/full",
|
||||
"test:ci": "bazelisk test --test_tag_filters=-only-template-pipeline -- //... -//devtools/... -//aio/... && bazelisk test --build_tests_only --//packages/compiler:use_template_pipeline --test_tag_filters=only-template-pipeline,also-template-pipeline //packages/...",
|
||||
"test-tsec": "bazelisk test //... --build_tag_filters=tsec --test_tag_filters=tsec",
|
||||
"lint": "yarn -s tslint && yarn -s ng-dev format changed --check",
|
||||
"tslint": "tslint -c tslint.json --project tsconfig-tslint.json",
|
||||
|
||||
@@ -41,6 +41,9 @@ export class BabelAstFactory implements AstFactory<t.Statement, t.Expression> {
|
||||
case '||':
|
||||
case '??':
|
||||
return t.logicalExpression(operator, leftOperand, rightOperand);
|
||||
case '=':
|
||||
// TODO(signals): Should `WriteVarExpr` be used instead??
|
||||
return this.createAssignment(leftOperand, rightOperand);
|
||||
default:
|
||||
return t.binaryExpression(operator, leftOperand, rightOperand);
|
||||
}
|
||||
|
||||
@@ -77,17 +77,33 @@ export function extractDirectiveMetadata(
|
||||
// Construct the map of inputs both from the @Directive/@Component
|
||||
// decorator, and the decorated fields.
|
||||
const inputsFromMeta = parseInputsArray(clazz, directive, evaluator, reflector, refEmitter);
|
||||
const inputsFromFields = parseInputFields(
|
||||
let inputsFromFields = parseInputFields(
|
||||
clazz, filterToMembersWithDecorator(decoratedElements, 'Input', coreModule), evaluator,
|
||||
reflector, refEmitter);
|
||||
|
||||
// TODO(signals)
|
||||
inputsFromFields = {
|
||||
...inputsFromFields,
|
||||
...findAndParseSignalInputs(reflector, evaluator, refEmitter, coreModule, clazz, members)
|
||||
};
|
||||
const inputs = ClassPropertyMapping.fromMappedObject({...inputsFromMeta, ...inputsFromFields});
|
||||
|
||||
// And outputs.
|
||||
const outputsFromMeta = parseOutputsArray(directive, evaluator);
|
||||
const outputsFromFields = parseOutputFields(
|
||||
let outputsFromFields = parseOutputFields(
|
||||
filterToMembersWithDecorator(decoratedElements, 'Output', coreModule), evaluator);
|
||||
|
||||
// TODO(signals)
|
||||
outputsFromFields = {
|
||||
...outputsFromFields,
|
||||
...findAndParseSignalOutputs(reflector, evaluator, coreModule, members)
|
||||
};
|
||||
|
||||
const outputs = ClassPropertyMapping.fromMappedObject({...outputsFromMeta, ...outputsFromFields});
|
||||
|
||||
const signalQueryDefinitions =
|
||||
findAndParseSignalQueries(reflector, evaluator, coreModule, members);
|
||||
|
||||
// Construct the list of queries.
|
||||
const contentChildFromFields = queriesFromFields(
|
||||
filterToMembersWithDecorator(decoratedElements, 'ContentChild', coreModule), reflector,
|
||||
@@ -96,7 +112,8 @@ export function extractDirectiveMetadata(
|
||||
filterToMembersWithDecorator(decoratedElements, 'ContentChildren', coreModule), reflector,
|
||||
evaluator);
|
||||
|
||||
const queries = [...contentChildFromFields, ...contentChildrenFromFields];
|
||||
const queries =
|
||||
[...contentChildFromFields, ...contentChildrenFromFields, ...signalQueryDefinitions.content];
|
||||
|
||||
// Construct the list of view queries.
|
||||
const viewChildFromFields = queriesFromFields(
|
||||
@@ -105,7 +122,8 @@ export function extractDirectiveMetadata(
|
||||
const viewChildrenFromFields = queriesFromFields(
|
||||
filterToMembersWithDecorator(decoratedElements, 'ViewChildren', coreModule), reflector,
|
||||
evaluator);
|
||||
const viewQueries = [...viewChildFromFields, ...viewChildrenFromFields];
|
||||
const viewQueries =
|
||||
[...viewChildFromFields, ...viewChildrenFromFields, ...signalQueryDefinitions.view];
|
||||
|
||||
if (directive.has('queries')) {
|
||||
const queriesFromDecorator =
|
||||
@@ -242,13 +260,13 @@ export function extractDirectiveMetadata(
|
||||
}
|
||||
|
||||
export function extractQueryMetadata(
|
||||
exprNode: ts.Node, name: string, args: ReadonlyArray<ts.Expression>, propertyName: string,
|
||||
exprNode: ts.Node, name: QueryType, args: ReadonlyArray<ts.Expression>, propertyName: string,
|
||||
reflector: ReflectionHost, evaluator: PartialEvaluator): R3QueryMetadata {
|
||||
if (args.length === 0) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
|
||||
ErrorCode.QUERY_DEFINITION_ARITY_WRONG, exprNode, `${name.forError} must have arguments`);
|
||||
}
|
||||
const first = name === 'ViewChild' || name === 'ContentChild';
|
||||
const first = name.first;
|
||||
const forwardReferenceTarget = tryUnwrapForwardRef(args[0], reflector);
|
||||
const node = forwardReferenceTarget ?? args[0];
|
||||
|
||||
@@ -266,23 +284,24 @@ export function extractQueryMetadata(
|
||||
forwardReferenceTarget !== null ? ForwardRefHandling.Unwrapped : ForwardRefHandling.None);
|
||||
} else if (typeof arg === 'string') {
|
||||
predicate = [arg];
|
||||
} else if (isStringArrayOrDie(arg, `@${name} predicate`, node)) {
|
||||
} else if (isStringArrayOrDie(arg, `${name.forError} predicate`, node)) {
|
||||
predicate = arg;
|
||||
} else {
|
||||
throw createValueHasWrongTypeError(node, arg, `@${name} predicate cannot be interpreted`);
|
||||
throw createValueHasWrongTypeError(
|
||||
node, arg, `${name.forError} predicate cannot be interpreted`);
|
||||
}
|
||||
|
||||
// Extract the read and descendants options.
|
||||
let read: Expression|null = null;
|
||||
// The default value for descendants is true for every decorator except @ContentChildren.
|
||||
let descendants: boolean = name !== 'ContentChildren';
|
||||
let descendants: boolean = !(name.type === 'content' && name.first === false);
|
||||
let emitDistinctChangesOnly: boolean = emitDistinctChangesOnlyDefaultValue;
|
||||
if (args.length === 2) {
|
||||
const optionsExpr = unwrapExpression(args[1]);
|
||||
if (!ts.isObjectLiteralExpression(optionsExpr)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARG_NOT_LITERAL, optionsExpr,
|
||||
`@${name} options must be an object literal`);
|
||||
ErrorCode.QUERY_DEFINITION_ARG_NOT_LITERAL, optionsExpr,
|
||||
`${name.forError} options must be an object literal`);
|
||||
}
|
||||
const options = reflectObjectLiteral(optionsExpr);
|
||||
if (options.has('read')) {
|
||||
@@ -294,7 +313,8 @@ export function extractQueryMetadata(
|
||||
const descendantsValue = evaluator.evaluate(descendantsExpr);
|
||||
if (typeof descendantsValue !== 'boolean') {
|
||||
throw createValueHasWrongTypeError(
|
||||
descendantsExpr, descendantsValue, `@${name} options.descendants must be a boolean`);
|
||||
descendantsExpr, descendantsValue,
|
||||
`${name.forError} options.descendants must be a boolean`);
|
||||
}
|
||||
descendants = descendantsValue;
|
||||
}
|
||||
@@ -305,7 +325,7 @@ export function extractQueryMetadata(
|
||||
if (typeof emitDistinctChangesOnlyValue !== 'boolean') {
|
||||
throw createValueHasWrongTypeError(
|
||||
emitDistinctChangesOnlyExpr, emitDistinctChangesOnlyValue,
|
||||
`@${name} options.emitDistinctChangesOnly must be a boolean`);
|
||||
`${name.forError} options.emitDistinctChangesOnly must be a boolean`);
|
||||
}
|
||||
emitDistinctChangesOnly = emitDistinctChangesOnlyValue;
|
||||
}
|
||||
@@ -314,7 +334,7 @@ export function extractQueryMetadata(
|
||||
const staticValue = evaluator.evaluate(options.get('static')!);
|
||||
if (typeof staticValue !== 'boolean') {
|
||||
throw createValueHasWrongTypeError(
|
||||
node, staticValue, `@${name} options.static must be a boolean`);
|
||||
node, staticValue, `${name.forError} options.static must be a boolean`);
|
||||
}
|
||||
isStatic = staticValue;
|
||||
}
|
||||
@@ -322,7 +342,7 @@ export function extractQueryMetadata(
|
||||
} else if (args.length > 2) {
|
||||
// Too many arguments.
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARITY_WRONG, node, `@${name} has too many arguments`);
|
||||
ErrorCode.QUERY_DEFINITION_ARITY_WRONG, node, `${name.forError} has too many arguments`);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -415,6 +435,29 @@ export function extractHostBindings(
|
||||
return bindings;
|
||||
}
|
||||
|
||||
interface QueryType {
|
||||
forError: string;
|
||||
first: boolean;
|
||||
type: 'view'|'content';
|
||||
}
|
||||
|
||||
function categorizeQueryByDecoratorName(name: string): QueryType {
|
||||
const forError = `@${name}`;
|
||||
|
||||
switch (name) {
|
||||
case 'ViewChild':
|
||||
return {first: true, type: 'view', forError};
|
||||
case 'ViewChildren':
|
||||
return {first: false, type: 'view', forError};
|
||||
case 'ContentChild':
|
||||
return {first: true, type: 'content', forError};
|
||||
case 'ContentChildren':
|
||||
return {first: false, type: 'content', forError};
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected query name: ${name}`);
|
||||
}
|
||||
|
||||
function extractQueriesFromDecorator(
|
||||
queryData: ts.Expression, reflector: ReflectionHost, evaluator: PartialEvaluator,
|
||||
isCore: boolean): {
|
||||
@@ -451,7 +494,8 @@ function extractQueriesFromDecorator(
|
||||
}
|
||||
|
||||
const query = extractQueryMetadata(
|
||||
queryExpr, type.name, queryExpr.arguments || [], propertyName, reflector, evaluator);
|
||||
queryExpr, categorizeQueryByDecoratorName(type.name), queryExpr.arguments || [],
|
||||
propertyName, reflector, evaluator);
|
||||
if (type.name.startsWith('Content')) {
|
||||
content.push(query);
|
||||
} else {
|
||||
@@ -558,7 +602,8 @@ function queriesFromFields(
|
||||
'Query decorator must go on a property-type member');
|
||||
}
|
||||
return extractQueryMetadata(
|
||||
node, decorator.name, decorator.args || [], member.name, reflector, evaluator);
|
||||
node, categorizeQueryByDecoratorName(decorator.name), decorator.args || [], member.name,
|
||||
reflector, evaluator);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -771,8 +816,8 @@ function parseInputTransformFunction(
|
||||
}
|
||||
|
||||
// Skip over `this` parameters since they're typing the context, not the actual parameter.
|
||||
// `this` parameters are guaranteed to be first if they exist, and the only to distinguish them
|
||||
// is using the name, TS doesn't have a special AST for them.
|
||||
// `this` parameters are guaranteed to be first if they exist, and the only to distinguish
|
||||
// them is using the name, TS doesn't have a special AST for them.
|
||||
const firstParam = definition.parameters[0]?.name === 'this' ? definition.parameters[1] :
|
||||
definition.parameters[0];
|
||||
|
||||
@@ -904,6 +949,201 @@ function evaluateHostExpressionBindings(
|
||||
return bindings;
|
||||
}
|
||||
|
||||
function getOptionsExpressionForInputCall(call: ts.CallExpression): ts.Expression|null {
|
||||
if (call.arguments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (call.arguments.length === 2) {
|
||||
return call.arguments[1];
|
||||
}
|
||||
|
||||
// If the first argument is not an object expression, it's an initial value-
|
||||
// but not the options argument.
|
||||
// TODO(signals): Might be able to use partial evaluator here.. need to decide on that.
|
||||
if (!ts.isObjectLiteralExpression(unwrapExpression(call.arguments[0]))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return call.arguments[0];
|
||||
}
|
||||
|
||||
// TODO(signals)
|
||||
function findAndParseSignalInputs(
|
||||
reflector: ReflectionHost, evaluator: PartialEvaluator, refEmitter: ReferenceEmitter,
|
||||
coreModule: string|undefined, clazz: ClassDeclaration,
|
||||
members: ClassMember[]): Record<string, InputMapping> {
|
||||
const res: Record<string, InputMapping> = {};
|
||||
|
||||
for (const m of members) {
|
||||
if (m.value === null) {
|
||||
continue;
|
||||
}
|
||||
const value = unwrapExpression(m.value);
|
||||
if (!ts.isCallExpression(value)) {
|
||||
continue;
|
||||
}
|
||||
const callTarget = unwrapExpression(value.expression);
|
||||
if (!ts.isIdentifier(callTarget)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isCoreSymbolReference(callTarget, 'input', reflector, coreModule)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const optionsNode = getOptionsExpressionForInputCall(value);
|
||||
const options = optionsNode === null ? null : evaluator.evaluate(optionsNode);
|
||||
|
||||
if (options !== null && !(options instanceof Map)) {
|
||||
// TODO(signals): proper diagnostic
|
||||
throw new Error('Input options are not an object..');
|
||||
}
|
||||
|
||||
let transform: InputTransform|null = null;
|
||||
|
||||
if (options?.has('transform')) {
|
||||
const transformValue = options.get('transform');
|
||||
|
||||
if (!(transformValue instanceof DynamicValue) && !(transformValue instanceof Reference)) {
|
||||
throw createValueHasWrongTypeError(
|
||||
optionsNode!, transformValue, `Input transform must be a function`);
|
||||
}
|
||||
|
||||
transform = parseInputTransformFunction(clazz, m.name, transformValue, reflector, refEmitter);
|
||||
}
|
||||
|
||||
res[m.name] = {
|
||||
classPropertyName: m.name,
|
||||
bindingPropertyName: options?.get('alias')?.toString() /* TODO */ ?? m.name,
|
||||
required: !!options?.get('required'),
|
||||
transform,
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// TODO(signals)
|
||||
function findAndParseSignalOutputs(
|
||||
reflector: ReflectionHost, evaluator: PartialEvaluator, coreModule: string|undefined,
|
||||
members: ClassMember[]): Record<string, string> {
|
||||
const res: Record<string, string> = {};
|
||||
|
||||
for (const m of members) {
|
||||
if (m.value === null) {
|
||||
continue;
|
||||
}
|
||||
const value = unwrapExpression(m.value);
|
||||
if (!ts.isCallExpression(value)) {
|
||||
continue;
|
||||
}
|
||||
const callTarget = unwrapExpression(value.expression);
|
||||
if (!ts.isIdentifier(callTarget)) {
|
||||
continue;
|
||||
}
|
||||
if (!isCoreSymbolReference(callTarget, 'output', reflector, coreModule)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const propertyName = m.name;
|
||||
const optionsNode = value.arguments[0];
|
||||
let publicOutputName = propertyName;
|
||||
|
||||
// If there is an options object, we look for a potential `alias`.
|
||||
if (optionsNode !== undefined) {
|
||||
const options = evaluator.evaluate(optionsNode);
|
||||
if (!(options instanceof Map)) {
|
||||
throw createValueHasWrongTypeError(
|
||||
optionsNode, options, `Output options must be an object.`);
|
||||
}
|
||||
|
||||
const alias = options.get('alias');
|
||||
if (alias !== undefined) {
|
||||
if (typeof alias !== 'string') {
|
||||
throw createValueHasWrongTypeError(
|
||||
optionsNode, options, `Alias must resolve to a string`);
|
||||
}
|
||||
publicOutputName = alias;
|
||||
}
|
||||
}
|
||||
|
||||
res[propertyName] = publicOutputName;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function findAndParseSignalQueries(
|
||||
reflector: ReflectionHost, evaluator: PartialEvaluator, coreModule: string|undefined,
|
||||
members: ClassMember[]): {view: R3QueryMetadata[], content: R3QueryMetadata[]} {
|
||||
const res: {view: R3QueryMetadata[], content: R3QueryMetadata[]} = {view: [], content: []};
|
||||
|
||||
for (const m of members) {
|
||||
if (m.value === null) {
|
||||
continue;
|
||||
}
|
||||
const value = unwrapExpression(m.value);
|
||||
if (!ts.isCallExpression(value)) {
|
||||
continue;
|
||||
}
|
||||
const callTarget = unwrapExpression(value.expression);
|
||||
if (!ts.isIdentifier(callTarget)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const viewChild: QueryType|false =
|
||||
isCoreSymbolReference(callTarget, 'viewChild', reflector, coreModule) && {
|
||||
first: true,
|
||||
forError: 'viewChild()',
|
||||
type: 'view',
|
||||
};
|
||||
|
||||
const viewChildren: QueryType|false =
|
||||
isCoreSymbolReference(callTarget, 'viewChildren', reflector, coreModule) && {
|
||||
first: false,
|
||||
forError: 'viewChildren()',
|
||||
type: 'view',
|
||||
};
|
||||
|
||||
const contentChild: QueryType|false =
|
||||
isCoreSymbolReference(callTarget, 'contentChild', reflector, coreModule) && {
|
||||
first: true,
|
||||
forError: 'contentChild()',
|
||||
type: 'content',
|
||||
};
|
||||
|
||||
const contentChildren: QueryType|false =
|
||||
isCoreSymbolReference(callTarget, 'contentChildren', reflector, coreModule) && {
|
||||
first: false,
|
||||
forError: 'contentChildren()',
|
||||
type: 'content',
|
||||
};
|
||||
|
||||
const query = viewChild || viewChildren || contentChild || contentChildren;
|
||||
|
||||
if (query === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const metadata =
|
||||
extractQueryMetadata(callTarget, query, value.arguments, m.name, reflector, evaluator);
|
||||
|
||||
if (query.type === 'view') {
|
||||
res.view.push(metadata);
|
||||
} else {
|
||||
res.content.push(metadata);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function isCoreSymbolReference(
|
||||
callTarget: ts.Identifier, name: string, reflector: ReflectionHost,
|
||||
coreModule: string|undefined): boolean {
|
||||
const imp = reflector.getImportOfIdentifier(callTarget);
|
||||
return imp !== null ? imp.from === coreModule && imp.name === name :
|
||||
callTarget.text === name && coreModule === undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and prepares the host directives metadata from an array literal expression.
|
||||
* @param rawHostDirectives Expression that defined the `hostDirectives`.
|
||||
|
||||
@@ -404,6 +404,14 @@ export enum ErrorCode {
|
||||
*/
|
||||
INJECTABLE_DUPLICATE_PROV = 9001,
|
||||
|
||||
/** Invalid number of arguments when defining a query, either using decorator or signal APIs. */
|
||||
QUERY_DEFINITION_ARITY_WRONG = 9002,
|
||||
|
||||
/**
|
||||
* An argument for defining a query is expected to be a literal, but turned out to be different.
|
||||
*/
|
||||
QUERY_DEFINITION_ARG_NOT_LITERAL = 9003,
|
||||
|
||||
// 10XXX error codes are reserved for diagnostics with categories other than
|
||||
// `ts.DiagnosticCategory.Error`. These diagnostics are generated by the compiler when configured
|
||||
// to do so by a tool such as the Language Service, or by the Language Service itself.
|
||||
|
||||
@@ -119,3 +119,45 @@ export interface OnDestroy {
|
||||
export interface TrackByFunction<T> {
|
||||
<U extends T>(index: number, item: T&U): any;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* SIGNALS / REACTIVITY
|
||||
*/
|
||||
|
||||
export type Signal<T> = () => T&{__signal: true};
|
||||
export type WritableSignal<T> = Signal<T>&{
|
||||
set(v: T): void;
|
||||
};
|
||||
|
||||
export function signal<T>(_initialValue: T): WritableSignal<T> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export function viewChild<T>(
|
||||
_selector: object|InjectionToken<T>|Function|string,
|
||||
_opts?: {read?: any, static?: boolean}): Signal<T|undefined> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export function viewChildren<T>(
|
||||
_selector: object|InjectionToken<T>|Function|string,
|
||||
_opts?: {read?: any, emitDistinctChangesOnly?: boolean}): Signal<T[]> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export function contentChild<T>(
|
||||
_selector: object|InjectionToken<T>|string,
|
||||
_opts?: {descendants?: boolean, read?: any, static?: boolean}): Signal<T|undefined> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export function contentChildren<T>(
|
||||
_selector: object|InjectionToken<T>|string,
|
||||
_opts?: {descendants?: boolean, read?: any, emitDistinctChangesOnly?: boolean}): Signal<T[]> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
export function output<T>(_opts?: {isAsync?: boolean, alias?: string}): EventEmitter<T> {
|
||||
return null!;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ export type UnaryOperator = '+'|'-'|'!';
|
||||
* The binary operators supported by the `AstFactory`.
|
||||
*/
|
||||
export type BinaryOperator =
|
||||
'&&'|'>'|'>='|'&'|'/'|'=='|'==='|'<'|'<='|'-'|'%'|'*'|'!='|'!=='|'||'|'+'|'??';
|
||||
'&&'|'>'|'>='|'&'|'/'|'='|'=='|'==='|'<'|'<='|'-'|'%'|'*'|'!='|'!=='|'||'|'+'|'??';
|
||||
|
||||
/**
|
||||
* The original location of the start or end of a node created by the `AstFactory`.
|
||||
|
||||
@@ -18,6 +18,7 @@ const UNARY_OPERATORS = new Map<o.UnaryOperator, UnaryOperator>([
|
||||
|
||||
const BINARY_OPERATORS = new Map<o.BinaryOperator, BinaryOperator>([
|
||||
[o.BinaryOperator.And, '&&'],
|
||||
[o.BinaryOperator.Assign, '='],
|
||||
[o.BinaryOperator.Bigger, '>'],
|
||||
[o.BinaryOperator.BiggerEquals, '>='],
|
||||
[o.BinaryOperator.BitwiseAnd, '&'],
|
||||
|
||||
@@ -31,6 +31,7 @@ const UNARY_OPERATORS: Record<UnaryOperator, ts.PrefixUnaryOperator> = {
|
||||
};
|
||||
|
||||
const BINARY_OPERATORS: Record<BinaryOperator, ts.BinaryOperator> = {
|
||||
'=': ts.SyntaxKind.EqualsToken,
|
||||
'&&': ts.SyntaxKind.AmpersandAmpersandToken,
|
||||
'>': ts.SyntaxKind.GreaterThanToken,
|
||||
'>=': ts.SyntaxKind.GreaterThanEqualsToken,
|
||||
|
||||
@@ -182,6 +182,11 @@ export class Environment implements ReferenceEmitEnvironment {
|
||||
type, this.contextFile, this.reflector, this.refEmitter, this.importManager);
|
||||
}
|
||||
|
||||
referenceExternalSymbol(moduleName: string, name: string): ts.Expression {
|
||||
const external = new ExternalExpr({moduleName, name});
|
||||
return translateExpression(external, this.importManager);
|
||||
}
|
||||
|
||||
getPreludeStatements(): ts.Statement[] {
|
||||
return [
|
||||
...this.pipeInstStatements,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AST, BindingPipe, BindingType, BoundTarget, Call, DYNAMIC_TYPE, ImplicitReceiver, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, SafeCall, SafePropertyRead, SchemaMetadata, ThisReceiver, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstElement, TmplAstForLoopBlock, TmplAstHoverDeferredTrigger, TmplAstIcu, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstInteractionDeferredTrigger, TmplAstNode, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable, TmplAstViewportDeferredTrigger, TransplantedType} from '@angular/compiler';
|
||||
import {AST, BindingPipe, BindingType, BoundTarget, Call, DYNAMIC_TYPE, ImplicitReceiver, ParsedEventType, ParseSourceSpan, PropertyRead, PropertyWrite, R3Identifiers, SafeCall, SafePropertyRead, SchemaMetadata, ThisReceiver, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstElement, TmplAstForLoopBlock, TmplAstHoverDeferredTrigger, TmplAstIcu, TmplAstIfBlock, TmplAstIfBlockBranch, TmplAstInteractionDeferredTrigger, TmplAstNode, TmplAstReference, TmplAstSwitchBlock, TmplAstSwitchBlockCase, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable, TmplAstViewportDeferredTrigger, TransplantedType} from '@angular/compiler';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
@@ -754,6 +754,8 @@ class TcbDirectiveInputsOp extends TcbOp {
|
||||
dirId = this.scope.resolve(this.node, this.dir);
|
||||
}
|
||||
|
||||
// TODO(signals): signal input with a restricted modifier
|
||||
|
||||
const id = this.tcb.allocateId();
|
||||
const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
|
||||
if (!ts.isTypeReferenceNode(dirTypeRef)) {
|
||||
@@ -771,14 +773,42 @@ class TcbDirectiveInputsOp extends TcbOp {
|
||||
dirId = this.scope.resolve(this.node, this.dir);
|
||||
}
|
||||
|
||||
// To get errors assign directly to the fields on the instance, using property access
|
||||
// when possible. String literal fields may not be valid JS identifiers so we use
|
||||
// literal element access instead for those cases.
|
||||
target = this.dir.stringLiteralInputFields.has(fieldName) ?
|
||||
ts.factory.createElementAccessExpression(
|
||||
dirId, ts.factory.createStringLiteral(fieldName)) :
|
||||
ts.factory.createPropertyAccessExpression(
|
||||
dirId, ts.factory.createIdentifier(fieldName));
|
||||
if (this.dir.isSignal) {
|
||||
const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
|
||||
if (!ts.isTypeReferenceNode(dirTypeRef)) {
|
||||
throw new Error(
|
||||
`Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`);
|
||||
}
|
||||
|
||||
const getSignalWriteType = this.tcb.env.referenceExternalType(
|
||||
R3Identifiers.getInputSignalWriteType.moduleName,
|
||||
R3Identifiers.getInputSignalWriteType.name);
|
||||
if (!ts.isTypeReferenceNode(getSignalWriteType)) {
|
||||
throw new Error(`Expected TypeReferenceNode from reference to ${
|
||||
R3Identifiers.getInputSignalWriteType.name}`);
|
||||
}
|
||||
|
||||
const inputFieldType = ts.factory.createIndexedAccessTypeNode(
|
||||
ts.factory.createTypeQueryNode(dirId as ts.Identifier),
|
||||
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(fieldName)));
|
||||
|
||||
const inputWriteTypeId = this.tcb.allocateId();
|
||||
const inputWriteTypeSt = tsDeclareVariable(
|
||||
inputWriteTypeId,
|
||||
ts.factory.createTypeReferenceNode(getSignalWriteType.typeName, [inputFieldType]));
|
||||
this.scope.addStatement(inputWriteTypeSt);
|
||||
|
||||
target = inputWriteTypeId;
|
||||
} else {
|
||||
// To get errors assign directly to the fields on the instance, using property access
|
||||
// when possible. String literal fields may not be valid JS identifiers so we use
|
||||
// literal element access instead for those cases.
|
||||
target = this.dir.stringLiteralInputFields.has(fieldName) ?
|
||||
ts.factory.createElementAccessExpression(
|
||||
dirId, ts.factory.createStringLiteral(fieldName)) :
|
||||
ts.factory.createPropertyAccessExpression(
|
||||
dirId, ts.factory.createIdentifier(fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
if (attr.attribute.keySpan !== undefined) {
|
||||
|
||||
@@ -18,6 +18,8 @@ jasmine_node_test(
|
||||
"//packages/compiler-cli/test/compliance/test_cases",
|
||||
],
|
||||
shard_count = 2,
|
||||
# Also run these tests with the template pipeline.
|
||||
tags = ["also-template-pipeline"],
|
||||
deps = [
|
||||
":test_lib",
|
||||
],
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: output_definition.js
|
||||
****************************************************************************************************/
|
||||
import { Component, output } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SensorComp {
|
||||
constructor() {
|
||||
this.pressed = output();
|
||||
this._internalName = output({ alias: 'touched' });
|
||||
}
|
||||
}
|
||||
SensorComp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SensorComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SensorComp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SensorComp, isStandalone: true, isSignal: true, selector: "ng-component", outputs: { pressed: "pressed", _internalName: "touched" }, ngImport: i0, template: '', isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SensorComp, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
signals: true,
|
||||
standalone: true,
|
||||
template: '',
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: output_definition.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SensorComp {
|
||||
pressed: import("@angular/core").EventEmitter<void>;
|
||||
_internalName: import("@angular/core").EventEmitter<void>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SensorComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SensorComp, "ng-component", never, {}, { "pressed": "pressed"; "_internalName": "touched"; }, never, never, true, never, true>;
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "../../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should detect output in a signal component",
|
||||
"inputFiles": ["output_definition.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "output_definition.js", "expected": "output_definition.js"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
SensorComp.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({
|
||||
// ...
|
||||
outputs: {
|
||||
pressed: "pressed",
|
||||
_internalName: "touched"
|
||||
},
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import {Component, output} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
signals: true,
|
||||
standalone: true,
|
||||
template: '',
|
||||
})
|
||||
export class SensorComp {
|
||||
pressed = output<void>();
|
||||
_internalName = output<void>({alias: 'touched'});
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: simple_binding.js
|
||||
****************************************************************************************************/
|
||||
import { Component, signal } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
LiteralValueBinding.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
LiteralValueBinding.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: LiteralValueBinding, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="true"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="true"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingStatic {
|
||||
constructor() {
|
||||
this.isDisabled = true;
|
||||
}
|
||||
}
|
||||
FromContextBindingStatic.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingStatic.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingStatic, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="isDisabled"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingSignal {
|
||||
constructor() {
|
||||
this.isDisabled = signal(true);
|
||||
}
|
||||
}
|
||||
FromContextBindingSignal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingSignal.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingSignal, isSignal: true, selector: "app", ngImport: i0, template: `<button [disabled]="isDisabled()"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled()"></button>`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: simple_binding.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class LiteralValueBinding {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<LiteralValueBinding, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<LiteralValueBinding, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingStatic {
|
||||
isDisabled: boolean;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingStatic, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingStatic, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingSignal {
|
||||
isDisabled: import("@angular/core").WritableSignal<boolean>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingSignal, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingSignal, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: with_interpolation.js
|
||||
****************************************************************************************************/
|
||||
import { Component, signal } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
LiteralValueBinding.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
LiteralValueBinding.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: LiteralValueBinding, isSignal: true, selector: "app", ngImport: i0, template: `<button title="This is a {{'submit'}} button"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LiteralValueBinding, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{'submit'}} button"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingStatic {
|
||||
constructor() {
|
||||
this.type = 'submit';
|
||||
}
|
||||
}
|
||||
FromContextBindingStatic.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingStatic.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingStatic, isSignal: true, selector: "app", ngImport: i0, template: `<button title="This is a {{type}} button"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingStatic, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type}} button"></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class FromContextBindingSignal {
|
||||
constructor() {
|
||||
this.type = signal('submit');
|
||||
}
|
||||
}
|
||||
FromContextBindingSignal.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
FromContextBindingSignal.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: FromContextBindingSignal, isSignal: true, selector: "app", ngImport: i0, template: `<button title="This is a {{type()}} button"></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FromContextBindingSignal, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type()}} button"></button>`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: with_interpolation.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class LiteralValueBinding {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<LiteralValueBinding, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<LiteralValueBinding, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingStatic {
|
||||
type: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingStatic, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingStatic, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class FromContextBindingSignal {
|
||||
type: import("@angular/core").WritableSignal<string>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<FromContextBindingSignal, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<FromContextBindingSignal, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support binding to DOM properties",
|
||||
"inputFiles": ["simple_binding.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "simple_binding.js", "expected": "simple_binding.js"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support interpolation binding to DOM properties",
|
||||
"inputFiles": ["with_interpolation.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "with_interpolation.js", "expected": "with_interpolation.js"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
template: function LiteralValueBinding_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button", 0);
|
||||
i0.ɵɵpropertyCreate(1, "disabled", function () { return true; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
template: function FromContextBindingStatic_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button", 0);
|
||||
i0.ɵɵpropertyCreate(1, "disabled", function () { return ctx.isDisabled; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
template: function FromContextBindingSignal_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button", 0);
|
||||
i0.ɵɵpropertyCreate(1, "disabled", function () { return ctx.isDisabled(); });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import {Component, signal} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="true"></button>`,
|
||||
})
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled"></button>`,
|
||||
})
|
||||
export class FromContextBindingStatic {
|
||||
isDisabled = true;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button [disabled]="isDisabled()"></button>`,
|
||||
})
|
||||
export class FromContextBindingSignal {
|
||||
isDisabled = signal(true);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
template: function LiteralValueBinding_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button", 0);
|
||||
i0.ɵɵpropertyCreate(1, "title", function () { return i0.ɵɵstringifyInterpolation`This is a ${"submit"} button`; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
template: function FromContextBindingStatic_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button", 0);
|
||||
i0.ɵɵpropertyCreate(1, "title", function () { return i0.ɵɵstringifyInterpolation`This is a ${ctx.type} button`; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
template: function FromContextBindingSignal_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "button", 0);
|
||||
i0.ɵɵpropertyCreate(1, "title", function () { return i0.ɵɵstringifyInterpolation`This is a ${ctx.type()} button`; });
|
||||
i0.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import {Component, signal} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{'submit'}} button"></button>`,
|
||||
})
|
||||
export class LiteralValueBinding {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type}} button"></button>`,
|
||||
})
|
||||
export class FromContextBindingStatic {
|
||||
type = 'submit';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button title="This is a {{type()}} button"></button>`,
|
||||
})
|
||||
export class FromContextBindingSignal {
|
||||
type = signal('submit');
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: view_child.js
|
||||
****************************************************************************************************/
|
||||
import { Component, viewChild } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleViewChildWithName {
|
||||
constructor() {
|
||||
this.buttonEl = viewChild('button');
|
||||
}
|
||||
}
|
||||
SimpleViewChildWithName.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildWithName, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleViewChildWithName.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleViewChildWithName, isSignal: true, selector: "app", viewQueries: [{ propertyName: "buttonEl", first: true, predicate: ["button"], descendants: true }], ngImport: i0, template: `<button #button></button>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildWithName, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button #button></button>`,
|
||||
}]
|
||||
}] });
|
||||
export class ButtonComp {
|
||||
}
|
||||
ButtonComp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
ButtonComp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: ButtonComp, isStandalone: true, selector: "button-comp", ngImport: i0, template: '', isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, decorators: [{
|
||||
type: Component,
|
||||
args: [{ selector: 'button-comp', template: '', standalone: true }]
|
||||
}] });
|
||||
export class SimpleViewChildWithType {
|
||||
constructor() {
|
||||
this.buttonEl = viewChild(ButtonComp);
|
||||
}
|
||||
}
|
||||
SimpleViewChildWithType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildWithType, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleViewChildWithType.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleViewChildWithType, isStandalone: true, isSignal: true, selector: "app", viewQueries: [{ propertyName: "buttonEl", first: true, predicate: ButtonComp, descendants: true }], ngImport: i0, template: `<button-comp></button-comp>`, isInline: true, dependencies: [{ kind: "component", type: ButtonComp, selector: "button-comp" }] });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildWithType, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
standalone: true,
|
||||
imports: [ButtonComp],
|
||||
template: `<button-comp></button-comp>`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: view_child.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleViewChildWithName {
|
||||
buttonEl: import("@angular/core").Signal<unknown>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleViewChildWithName, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleViewChildWithName, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class ButtonComp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ButtonComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<ButtonComp, "button-comp", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
export declare class SimpleViewChildWithType {
|
||||
buttonEl: import("@angular/core").Signal<unknown>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleViewChildWithType, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleViewChildWithType, "app", never, {}, {}, never, never, true, never, true>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: view_children.js
|
||||
****************************************************************************************************/
|
||||
import { Component, viewChildren } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleViewChildrenWithName {
|
||||
constructor() {
|
||||
this.buttonEls = viewChildren('buttons');
|
||||
}
|
||||
}
|
||||
SimpleViewChildrenWithName.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildrenWithName, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleViewChildrenWithName.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleViewChildrenWithName, isSignal: true, selector: "app", viewQueries: [{ propertyName: "buttonEls", predicate: ["buttons"], descendants: true }], ngImport: i0, template: `
|
||||
<button #buttons></button>
|
||||
<button #buttons></button>
|
||||
`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildrenWithName, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `
|
||||
<button #buttons></button>
|
||||
<button #buttons></button>
|
||||
`,
|
||||
}]
|
||||
}] });
|
||||
export class ButtonComp {
|
||||
}
|
||||
ButtonComp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
ButtonComp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: ButtonComp, isStandalone: true, selector: "button-comp", ngImport: i0, template: '', isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, decorators: [{
|
||||
type: Component,
|
||||
args: [{ selector: 'button-comp', template: '', standalone: true }]
|
||||
}] });
|
||||
export class SimpleViewChildrenWithType {
|
||||
constructor() {
|
||||
this.buttonEls = viewChildren(ButtonComp);
|
||||
}
|
||||
}
|
||||
SimpleViewChildrenWithType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildrenWithType, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleViewChildrenWithType.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleViewChildrenWithType, isStandalone: true, isSignal: true, selector: "app", viewQueries: [{ propertyName: "buttonEls", predicate: ButtonComp, descendants: true }], ngImport: i0, template: `
|
||||
<button-comp></button-comp>
|
||||
<button-comp></button-comp>
|
||||
`, isInline: true, dependencies: [{ kind: "component", type: ButtonComp, selector: "button-comp" }] });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleViewChildrenWithType, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
standalone: true,
|
||||
imports: [ButtonComp],
|
||||
template: `
|
||||
<button-comp></button-comp>
|
||||
<button-comp></button-comp>
|
||||
`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: view_children.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleViewChildrenWithName {
|
||||
buttonEls: import("@angular/core").Signal<unknown[]>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleViewChildrenWithName, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleViewChildrenWithName, "app", never, {}, {}, never, never, false, never, true>;
|
||||
}
|
||||
export declare class ButtonComp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ButtonComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<ButtonComp, "button-comp", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
export declare class SimpleViewChildrenWithType {
|
||||
buttonEls: import("@angular/core").Signal<unknown[]>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleViewChildrenWithType, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleViewChildrenWithType, "app", never, {}, {}, never, never, true, never, true>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: content_child.js
|
||||
****************************************************************************************************/
|
||||
import { Component, contentChild } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleContentChildWithName {
|
||||
constructor() {
|
||||
this.buttonEl = contentChild('button');
|
||||
}
|
||||
}
|
||||
SimpleContentChildWithName.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildWithName, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleContentChildWithName.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleContentChildWithName, isSignal: true, selector: "app", queries: [{ propertyName: "buttonEl", first: true, predicate: ["button"], descendants: true }], ngImport: i0, template: `{{buttonEl() !== undefined}}`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildWithName, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonEl() !== undefined}}`,
|
||||
}]
|
||||
}] });
|
||||
export class ButtonComp {
|
||||
}
|
||||
ButtonComp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
ButtonComp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: ButtonComp, isStandalone: true, selector: "button-comp", ngImport: i0, template: '', isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, decorators: [{
|
||||
type: Component,
|
||||
args: [{ selector: 'button-comp', template: '', standalone: true }]
|
||||
}] });
|
||||
export class SimpleContentChildWithType {
|
||||
constructor() {
|
||||
this.buttonComp = contentChild(ButtonComp);
|
||||
}
|
||||
}
|
||||
SimpleContentChildWithType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildWithType, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleContentChildWithType.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleContentChildWithType, isSignal: true, selector: "app", queries: [{ propertyName: "buttonComp", first: true, predicate: ButtonComp, descendants: true }], ngImport: i0, template: `{{buttonComp() !== undefined}}`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildWithType, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonComp() !== undefined}}`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: content_child.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleContentChildWithName {
|
||||
buttonEl: import("@angular/core").Signal<unknown>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleContentChildWithName, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleContentChildWithName, "app", never, {}, {}, ["buttonEl"], never, false, never, true>;
|
||||
}
|
||||
export declare class ButtonComp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ButtonComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<ButtonComp, "button-comp", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
export declare class SimpleContentChildWithType {
|
||||
buttonComp: import("@angular/core").Signal<unknown>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleContentChildWithType, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleContentChildWithType, "app", never, {}, {}, ["buttonComp"], never, false, never, true>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: content_children.js
|
||||
****************************************************************************************************/
|
||||
import { Component, contentChildren } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleContentChildrenWithName {
|
||||
constructor() {
|
||||
this.buttonEls = contentChildren('button');
|
||||
}
|
||||
}
|
||||
SimpleContentChildrenWithName.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildrenWithName, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleContentChildrenWithName.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleContentChildrenWithName, isSignal: true, selector: "app", queries: [{ propertyName: "buttonEls", predicate: ["button"] }], ngImport: i0, template: `{{buttonEls().length}}`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildrenWithName, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonEls().length}}`,
|
||||
}]
|
||||
}] });
|
||||
export class ButtonComp {
|
||||
}
|
||||
ButtonComp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
ButtonComp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: ButtonComp, isStandalone: true, selector: "button-comp", ngImport: i0, template: '', isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ButtonComp, decorators: [{
|
||||
type: Component,
|
||||
args: [{ selector: 'button-comp', template: '', standalone: true }]
|
||||
}] });
|
||||
export class SimpleContentChildrenWithType {
|
||||
constructor() {
|
||||
this.buttonComps = contentChildren(ButtonComp);
|
||||
}
|
||||
}
|
||||
SimpleContentChildrenWithType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildrenWithType, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleContentChildrenWithType.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: SimpleContentChildrenWithType, isSignal: true, selector: "app", queries: [{ propertyName: "buttonComps", predicate: ButtonComp }], ngImport: i0, template: `{{buttonComps().length}}`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleContentChildrenWithType, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonComps().length}}`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: content_children.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleContentChildrenWithName {
|
||||
buttonEls: import("@angular/core").Signal<unknown[]>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleContentChildrenWithName, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleContentChildrenWithName, "app", never, {}, {}, ["buttonEls"], never, false, never, true>;
|
||||
}
|
||||
export declare class ButtonComp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ButtonComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<ButtonComp, "button-comp", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
export declare class SimpleContentChildrenWithType {
|
||||
buttonComps: import("@angular/core").Signal<unknown[]>;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleContentChildrenWithType, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleContentChildrenWithType, "app", never, {}, {}, ["buttonComps"], never, false, never, true>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support querying for a view child",
|
||||
"inputFiles": ["view_child.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "view_child.js", "expected": "view_child.js"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support querying for multiple view children",
|
||||
"inputFiles": ["view_children.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "view_children.js", "expected": "view_children.js"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support querying for a content child",
|
||||
"inputFiles": ["content_child.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "content_child.js", "expected": "content_child.js"}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"onlyForTemplatePipeline": true,
|
||||
"description": "should support querying for multiple content children",
|
||||
"inputFiles": ["content_children.ts"],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [{"generated": "content_children.js", "expected": "content_children.js"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
const _c0 = ["button"];
|
||||
// ...
|
||||
contentQueries:
|
||||
function SimpleContentChildWithName_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵcontentQueryCreate(ctx.buttonEl, dirIndex, _c0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
contentQueries: function SimpleContentChildWithType_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵcontentQueryCreate(ctx.buttonComp, dirIndex, ButtonComp, 5);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import {Component, contentChild} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonEl() !== undefined}}`,
|
||||
})
|
||||
export class SimpleContentChildWithName {
|
||||
buttonEl = contentChild('button');
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'button-comp', template: '', standalone: true})
|
||||
export class ButtonComp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonComp() !== undefined}}`,
|
||||
})
|
||||
export class SimpleContentChildWithType {
|
||||
buttonComp = contentChild(ButtonComp);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
const _c0 = ["button"];
|
||||
// ...
|
||||
contentQueries:
|
||||
function SimpleContentChildrenWithName_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵcontentQueryCreate(ctx.buttonEls, dirIndex, _c0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
contentQueries: function SimpleContentChildrenWithType_ContentQueries(rf, ctx, dirIndex) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵcontentQueryCreate(ctx.buttonComps, dirIndex, ButtonComp, 4);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import {Component, contentChildren} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonEls().length}}`,
|
||||
})
|
||||
export class SimpleContentChildrenWithName {
|
||||
buttonEls = contentChildren('button');
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'button-comp', template: '', standalone: true})
|
||||
export class ButtonComp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `{{buttonComps().length}}`,
|
||||
})
|
||||
export class SimpleContentChildrenWithType {
|
||||
buttonComps = contentChildren(ButtonComp);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
const _c0 = ["button"];
|
||||
// ...
|
||||
viewQuery: function SimpleViewChildWithName_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵviewQueryCreate(ctx.buttonEl, _c0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
viewQuery: function SimpleViewChildWithType_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵviewQueryCreate(ctx.buttonEl, ButtonComp, 5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import {Component, viewChild} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `<button #button></button>`,
|
||||
})
|
||||
export class SimpleViewChildWithName {
|
||||
buttonEl = viewChild('button');
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'button-comp', template: '', standalone: true})
|
||||
export class ButtonComp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
standalone: true,
|
||||
imports: [ButtonComp],
|
||||
template: `<button-comp></button-comp>`,
|
||||
})
|
||||
export class SimpleViewChildWithType {
|
||||
buttonEl = viewChild(ButtonComp);
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
const _c0 = ["buttons"];
|
||||
// ...
|
||||
viewQuery: function SimpleViewChildrenWithName_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵviewQueryCreate(ctx.buttonEls, _c0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
viewQuery: function SimpleViewChildrenWithType_Query(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵviewQueryCreate(ctx.buttonEls, ButtonComp, 5);
|
||||
}
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import {Component, viewChildren} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
template: `
|
||||
<button #buttons></button>
|
||||
<button #buttons></button>
|
||||
`,
|
||||
})
|
||||
export class SimpleViewChildrenWithName {
|
||||
buttonEls = viewChildren('buttons');
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'button-comp', template: '', standalone: true})
|
||||
export class ButtonComp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
signals: true,
|
||||
standalone: true,
|
||||
imports: [ButtonComp],
|
||||
template: `
|
||||
<button-comp></button-comp>
|
||||
<button-comp></button-comp>
|
||||
`,
|
||||
})
|
||||
export class SimpleViewChildrenWithType {
|
||||
buttonEls = viewChildren(ButtonComp);
|
||||
}
|
||||
@@ -2045,7 +2045,7 @@ function allTests(os: string) {
|
||||
}
|
||||
`);
|
||||
verifyThrownError(
|
||||
ErrorCode.DECORATOR_ARG_NOT_LITERAL,
|
||||
ErrorCode.QUERY_DEFINITION_ARG_NOT_LITERAL,
|
||||
`@${decorator} options must be an object literal`);
|
||||
});
|
||||
|
||||
@@ -2081,7 +2081,7 @@ function allTests(os: string) {
|
||||
}
|
||||
`);
|
||||
verifyThrownError(
|
||||
ErrorCode.DECORATOR_ARITY_WRONG, `@${decorator} has too many arguments`);
|
||||
ErrorCode.QUERY_DEFINITION_ARITY_WRONG, `@${decorator} has too many arguments`);
|
||||
});
|
||||
|
||||
it(`should throw error if @${decorator} predicate argument has wrong type`, () => {
|
||||
|
||||
@@ -451,6 +451,9 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||
case o.BinaryOperator.NullishCoalesce:
|
||||
opStr = '??';
|
||||
break;
|
||||
case o.BinaryOperator.Assign:
|
||||
opStr = '=';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown operator ${ast.operator}`);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ export enum UnaryOperator {
|
||||
}
|
||||
|
||||
export enum BinaryOperator {
|
||||
Assign,
|
||||
Equals,
|
||||
NotEquals,
|
||||
Identical,
|
||||
|
||||
@@ -249,6 +249,7 @@ export class Identifiers {
|
||||
static projectionDef: o.ExternalReference = {name: 'ɵɵprojectionDef', moduleName: CORE};
|
||||
|
||||
static reference: o.ExternalReference = {name: 'ɵɵreference', moduleName: CORE};
|
||||
static shallowReference: o.ExternalReference = {name: 'ɵɵshallowReference', moduleName: CORE};
|
||||
|
||||
static inject: o.ExternalReference = {name: 'ɵɵinject', moduleName: CORE};
|
||||
|
||||
@@ -389,4 +390,16 @@ export class Identifiers {
|
||||
o.ExternalReference = {name: 'ɵɵtrustConstantResourceUrl', moduleName: CORE};
|
||||
static validateIframeAttribute:
|
||||
o.ExternalReference = {name: 'ɵɵvalidateIframeAttribute', moduleName: CORE};
|
||||
|
||||
// ## signal APIs
|
||||
|
||||
// for type-checking
|
||||
static getInputSignalWriteType = {name: 'ɵɵGetInputSignalWriteType', moduleName: CORE};
|
||||
|
||||
static propertyCreate: o.ExternalReference = {name: 'ɵɵpropertyCreate', moduleName: CORE};
|
||||
static stringifyInterpolation:
|
||||
o.ExternalReference = {name: 'ɵɵstringifyInterpolation', moduleName: CORE};
|
||||
|
||||
static viewQueryCreate: o.ExternalReference = {name: 'ɵɵviewQueryCreate', moduleName: CORE};
|
||||
static contentQueryCreate: o.ExternalReference = {name: 'ɵɵcontentQueryCreate', moduleName: CORE};
|
||||
}
|
||||
|
||||
@@ -57,12 +57,14 @@ function baseDirectiveFields(
|
||||
if (meta.queries.length > 0) {
|
||||
// e.g. `contentQueries: (rf, ctx, dirIndex) => { ... }
|
||||
definitionMap.set(
|
||||
'contentQueries', createContentQueriesFunction(meta.queries, constantPool, meta.name));
|
||||
'contentQueries',
|
||||
createContentQueriesFunction(meta.isSignal, meta.queries, constantPool, meta.name));
|
||||
}
|
||||
|
||||
if (meta.viewQueries.length) {
|
||||
definitionMap.set(
|
||||
'viewQuery', createViewQueriesFunction(meta.viewQueries, constantPool, meta.name));
|
||||
'viewQuery',
|
||||
createViewQueriesFunction(meta.isSignal, meta.viewQueries, constantPool, meta.name));
|
||||
}
|
||||
|
||||
// e.g. `hostBindings: (rf, ctx) => { ... }
|
||||
@@ -70,7 +72,7 @@ function baseDirectiveFields(
|
||||
'hostBindings',
|
||||
createHostBindingsFunction(
|
||||
meta.host, meta.typeSourceSpan, bindingParser, constantPool, meta.selector || '',
|
||||
meta.name, definitionMap));
|
||||
meta.name, meta.isSignal, definitionMap));
|
||||
|
||||
// e.g 'inputs: {a: 'a'}`
|
||||
definitionMap.set('inputs', conditionallyCreateDirectiveBindingLiteral(meta.inputs, true));
|
||||
@@ -235,7 +237,7 @@ export function compileComponentFromMetadata(
|
||||
// This path compiles the template using the prototype template pipeline. First the template is
|
||||
// ingested into IR:
|
||||
const tpl = ingestComponent(
|
||||
meta.name, meta.template.nodes, constantPool, meta.relativeContextFilePath,
|
||||
meta.name, meta.isSignal, meta.template.nodes, constantPool, meta.relativeContextFilePath,
|
||||
meta.i18nUseExternalIds);
|
||||
|
||||
// Then the IR is transformed to prepare it for cod egeneration.
|
||||
@@ -432,12 +434,25 @@ function convertAttributesToExpressions(attributes: {[name: string]: o.Expressio
|
||||
|
||||
// Define and update any content queries
|
||||
function createContentQueriesFunction(
|
||||
queries: R3QueryMetadata[], constantPool: ConstantPool, name?: string): o.Expression {
|
||||
isSignal: boolean, queries: R3QueryMetadata[], constantPool: ConstantPool,
|
||||
name?: string): o.Expression {
|
||||
const createStatements: o.Statement[] = [];
|
||||
const updateStatements: o.Statement[] = [];
|
||||
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
|
||||
|
||||
for (const query of queries) {
|
||||
if (isSignal) {
|
||||
createStatements.push(
|
||||
o.importExpr(R3.contentQueryCreate)
|
||||
.callFn([
|
||||
o.variable(CONTEXT_NAME).prop(query.propertyName),
|
||||
// TODO(signals): `dirIndex` is not actually used at all. Can remove?
|
||||
o.variable('dirIndex'), ...prepareQueryParams(query, constantPool)
|
||||
])
|
||||
.toStmt());
|
||||
continue;
|
||||
}
|
||||
|
||||
// creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null);
|
||||
createStatements.push(
|
||||
o.importExpr(R3.contentQuery)
|
||||
@@ -455,16 +470,21 @@ function createContentQueriesFunction(
|
||||
}
|
||||
|
||||
const contentQueriesFnName = name ? `${name}_ContentQueries` : null;
|
||||
const body = [
|
||||
renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements),
|
||||
];
|
||||
|
||||
// Signal directives may not generate update statements.
|
||||
if (updateStatements.length > 0) {
|
||||
body.push(renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements));
|
||||
}
|
||||
|
||||
return o.fn(
|
||||
[
|
||||
new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null),
|
||||
new o.FnParam('dirIndex', null)
|
||||
],
|
||||
[
|
||||
renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements),
|
||||
renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements)
|
||||
],
|
||||
o.INFERRED_TYPE, null, contentQueriesFnName);
|
||||
body, o.INFERRED_TYPE, null, contentQueriesFnName);
|
||||
}
|
||||
|
||||
function stringAsType(str: string): o.Type {
|
||||
@@ -540,12 +560,23 @@ export function createDirectiveType(meta: R3DirectiveMetadata): o.Type {
|
||||
|
||||
// Define and update any view queries
|
||||
function createViewQueriesFunction(
|
||||
viewQueries: R3QueryMetadata[], constantPool: ConstantPool, name?: string): o.Expression {
|
||||
isSignal: boolean, viewQueries: R3QueryMetadata[], constantPool: ConstantPool,
|
||||
name?: string): o.Expression {
|
||||
const createStatements: o.Statement[] = [];
|
||||
const updateStatements: o.Statement[] = [];
|
||||
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
|
||||
|
||||
viewQueries.forEach((query: R3QueryMetadata) => {
|
||||
if (isSignal) {
|
||||
createStatements.push(o.importExpr(R3.viewQueryCreate)
|
||||
.callFn([
|
||||
o.variable(CONTEXT_NAME).prop(query.propertyName),
|
||||
...prepareQueryParams(query, constantPool)
|
||||
])
|
||||
.toStmt());
|
||||
return;
|
||||
}
|
||||
|
||||
// creation, e.g. r3.viewQuery(somePredicate, true);
|
||||
const queryDefinition =
|
||||
o.importExpr(R3.viewQuery).callFn(prepareQueryParams(query, constantPool));
|
||||
@@ -562,12 +593,17 @@ function createViewQueriesFunction(
|
||||
});
|
||||
|
||||
const viewQueryFnName = name ? `${name}_Query` : null;
|
||||
const body = [
|
||||
renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements),
|
||||
];
|
||||
|
||||
// Signal directives may not generate update statements.
|
||||
if (updateStatements.length > 0) {
|
||||
body.push(renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements));
|
||||
}
|
||||
|
||||
return o.fn(
|
||||
[new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)],
|
||||
[
|
||||
renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements),
|
||||
renderFlagCheckIfStmt(core.RenderFlags.Update, updateStatements)
|
||||
],
|
||||
[new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)], body,
|
||||
o.INFERRED_TYPE, null, viewQueryFnName);
|
||||
}
|
||||
|
||||
@@ -575,7 +611,7 @@ function createViewQueriesFunction(
|
||||
function createHostBindingsFunction(
|
||||
hostBindingsMetadata: R3HostMetadata, typeSourceSpan: ParseSourceSpan,
|
||||
bindingParser: BindingParser, constantPool: ConstantPool, selector: string, name: string,
|
||||
definitionMap: DefinitionMap): o.Expression|null {
|
||||
isSignal: boolean, definitionMap: DefinitionMap): o.Expression|null {
|
||||
const bindings =
|
||||
bindingParser.createBoundHostProperties(hostBindingsMetadata.properties, typeSourceSpan);
|
||||
|
||||
@@ -599,12 +635,13 @@ function createHostBindingsFunction(
|
||||
|
||||
const hostJob = ingestHostBinding(
|
||||
{
|
||||
isSignal,
|
||||
componentName: name,
|
||||
properties: bindings,
|
||||
events: eventBindings,
|
||||
attributes: hostBindingsMetadata.attributes,
|
||||
},
|
||||
bindingParser, constantPool);
|
||||
constantPool);
|
||||
transform(hostJob, CompilationJobKind.Host);
|
||||
|
||||
definitionMap.set('hostAttrs', hostJob.root.attributes);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
export * from './src/enums';
|
||||
export * from './src/interpolation';
|
||||
export * from './src/expression';
|
||||
export * from './src/operations';
|
||||
export * from './src/ops/create';
|
||||
|
||||
@@ -100,6 +100,12 @@ export enum OpKind {
|
||||
*/
|
||||
Binding,
|
||||
|
||||
/**
|
||||
* A placeholder operation reserving space for `Binding` operations that end up being
|
||||
* moved into the creation block phase for signal components.
|
||||
*/
|
||||
BindingSignalPlaceholder,
|
||||
|
||||
/**
|
||||
* An operation to bind an expression to a property of an element.
|
||||
*/
|
||||
@@ -209,6 +215,11 @@ export enum OpKind {
|
||||
* An instruction that applies a set of i18n expressions.
|
||||
*/
|
||||
I18nApply,
|
||||
|
||||
/*
|
||||
* TODO
|
||||
*/
|
||||
PropertyCreate,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,6 +251,11 @@ export enum ExpressionKind {
|
||||
*/
|
||||
Reference,
|
||||
|
||||
/**
|
||||
* Runtime operation to retrieve the value of a shallow local reference.
|
||||
*/
|
||||
ShallowReference,
|
||||
|
||||
/**
|
||||
* Runtime operation to snapshot the current view context.
|
||||
*/
|
||||
@@ -308,6 +324,7 @@ export enum ExpressionKind {
|
||||
/**
|
||||
* A reference to a temporary variable.
|
||||
*/
|
||||
|
||||
ReadTemporaryExpr,
|
||||
|
||||
/**
|
||||
@@ -324,6 +341,12 @@ export enum ExpressionKind {
|
||||
* A test expression for a conditional op.
|
||||
*/
|
||||
ConditionalCase,
|
||||
|
||||
/**
|
||||
* An interpolation template string expression. Such template string expression is
|
||||
* commonly expected to be part of the `PropertyCreate` operation expression.
|
||||
*/
|
||||
InterpolationTemplateExpr,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,23 +8,23 @@
|
||||
|
||||
import * as o from '../../../../output/output_ast';
|
||||
import type {ParseSourceSpan} from '../../../../parse_util';
|
||||
|
||||
import * as t from '../../../../render3/r3_ast';
|
||||
import {ExpressionKind, OpKind, SanitizerFn} from './enums';
|
||||
import {ConsumesVarsTrait, UsesSlotIndex, UsesSlotIndexTrait, UsesVarOffset, UsesVarOffsetTrait} from './traits';
|
||||
|
||||
import {ExpressionKind, OpKind, SanitizerFn} from './enums';
|
||||
import {Interpolation} from './interpolation';
|
||||
import type {XrefId} from './operations';
|
||||
import type {CreateOp} from './ops/create';
|
||||
import {Interpolation, type UpdateOp} from './ops/update';
|
||||
import {type UpdateOp} from './ops/update';
|
||||
import {ConsumesVarsTrait, UsesSlotIndex, UsesSlotIndexTrait, UsesVarOffset, UsesVarOffsetTrait} from './traits';
|
||||
|
||||
/**
|
||||
* An `o.Expression` subtype representing a logical expression in the intermediate representation.
|
||||
*/
|
||||
export type Expression =
|
||||
LexicalReadExpr|ReferenceExpr|ContextExpr|NextContextExpr|GetCurrentViewExpr|RestoreViewExpr|
|
||||
ResetViewExpr|ReadVariableExpr|PureFunctionExpr|PureFunctionParameterExpr|PipeBindingExpr|
|
||||
PipeBindingVariadicExpr|SafePropertyReadExpr|SafeKeyedReadExpr|SafeInvokeFunctionExpr|EmptyExpr|
|
||||
AssignTemporaryExpr|ReadTemporaryExpr|SanitizerExpr|SlotLiteralExpr|ConditionalCaseExpr;
|
||||
export type Expression = LexicalReadExpr|ReferenceExpr|ShallowReferenceExpr|ContextExpr|
|
||||
NextContextExpr|GetCurrentViewExpr|RestoreViewExpr|ResetViewExpr|ReadVariableExpr|
|
||||
PureFunctionExpr|PureFunctionParameterExpr|PipeBindingExpr|PipeBindingVariadicExpr|
|
||||
SafePropertyReadExpr|SafeKeyedReadExpr|SafeInvokeFunctionExpr|EmptyExpr|AssignTemporaryExpr|
|
||||
ReadTemporaryExpr|SanitizerExpr|SlotLiteralExpr|ConditionalCaseExpr|InterpolationTemplateExpr;
|
||||
|
||||
/**
|
||||
* Transformer type which converts expressions into general `o.Expression`s (which may be an
|
||||
@@ -117,6 +117,40 @@ export class ReferenceExpr extends ExpressionBase implements UsesSlotIndexTrait
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runtime operation to retrieve the value of a local reference.
|
||||
*/
|
||||
export class ShallowReferenceExpr extends ExpressionBase implements UsesSlotIndexTrait {
|
||||
override readonly kind = ExpressionKind.ShallowReference;
|
||||
|
||||
readonly[UsesSlotIndex] = true;
|
||||
|
||||
targetSlot: number|null = null;
|
||||
|
||||
constructor(readonly target: XrefId, readonly offset: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression(): void {}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof ShallowReferenceExpr && e.target === this.target;
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(): void {}
|
||||
|
||||
override clone(): ShallowReferenceExpr {
|
||||
const expr = new ShallowReferenceExpr(this.target, this.offset);
|
||||
expr.targetSlot = this.targetSlot;
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the current view context (usually the `ctx` variable in a template function).
|
||||
*/
|
||||
@@ -630,6 +664,47 @@ export class SafeTernaryExpr extends ExpressionBase {
|
||||
}
|
||||
}
|
||||
|
||||
export class InterpolationTemplateExpr extends ExpressionBase {
|
||||
override readonly kind = ExpressionKind.InterpolationTemplateExpr;
|
||||
|
||||
constructor(public staticParts: string[], public expressionParts: o.Expression[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression() {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
if (!(e instanceof InterpolationTemplateExpr)) {
|
||||
return false;
|
||||
}
|
||||
if (e.staticParts.length !== this.staticParts.length) {
|
||||
return false;
|
||||
}
|
||||
if (e.expressionParts.length !== this.expressionParts.length) {
|
||||
return false;
|
||||
}
|
||||
return e.staticParts.every((p, i) => p === this.staticParts[i]) &&
|
||||
e.expressionParts.every((e, i) => e === this.expressionParts[i]);
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(transform: ExpressionTransform, flags: VisitorContextFlag):
|
||||
void {
|
||||
this.expressionParts =
|
||||
this.expressionParts.map(p => transformExpressionsInExpression(p, transform, flags));
|
||||
}
|
||||
|
||||
override clone(): InterpolationTemplateExpr {
|
||||
return new InterpolationTemplateExpr(
|
||||
[...this.staticParts], this.expressionParts.map(p => p.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyExpr extends ExpressionBase {
|
||||
override readonly kind = ExpressionKind.EmptyExpr;
|
||||
|
||||
@@ -852,6 +927,7 @@ export function transformExpressionsInOp(
|
||||
}
|
||||
break;
|
||||
case OpKind.Property:
|
||||
case OpKind.PropertyCreate:
|
||||
case OpKind.Attribute:
|
||||
if (op.expression instanceof Interpolation) {
|
||||
transformExpressionsInInterpolation(op.expression, transform, flags);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 * as o from '../../../../output/output_ast';
|
||||
|
||||
export class Interpolation {
|
||||
constructor(readonly strings: string[], readonly expressions: o.Expression[]) {}
|
||||
}
|
||||
@@ -293,9 +293,6 @@ export class OpList<OpT extends Op<OpT>> {
|
||||
|
||||
op.debugListId = target.debugListId;
|
||||
|
||||
// Just in case.
|
||||
op.prev = null;
|
||||
|
||||
target.prev!.next = op;
|
||||
op.prev = target.prev;
|
||||
|
||||
|
||||
@@ -6,25 +6,26 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SecurityContext} from '../../../../../core';
|
||||
import * as i18n from '../../../../../i18n/i18n_ast';
|
||||
import * as o from '../../../../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../../../../parse_util';
|
||||
import {BindingKind, DeferSecondaryKind, OpKind} from '../enums';
|
||||
import {Interpolation} from '../interpolation';
|
||||
import {Op, OpList, XrefId} from '../operations';
|
||||
import {ConsumesSlotOpTrait, HasConstTrait, TRAIT_CONSUMES_SLOT, TRAIT_HAS_CONST, TRAIT_USES_SLOT_INDEX, UsesSlotIndexTrait} from '../traits';
|
||||
import {ConsumesSlotOpTrait, ConsumesVarsTrait, HasConstTrait, TRAIT_CONSUMES_SLOT, TRAIT_CONSUMES_VARS, TRAIT_HAS_CONST, TRAIT_USES_SLOT_INDEX, UsesSlotIndexTrait} from '../traits';
|
||||
|
||||
import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared';
|
||||
|
||||
import type {UpdateOp} from './update';
|
||||
|
||||
/**
|
||||
* An operation usable on the creation side of the IR.
|
||||
*/
|
||||
export type CreateOp =
|
||||
ListEndOp<CreateOp>|StatementOp<CreateOp>|ElementOp|ElementStartOp|ElementEndOp|ContainerOp|
|
||||
ContainerStartOp|ContainerEndOp|TemplateOp|EnableBindingsOp|DisableBindingsOp|TextOp|ListenerOp|
|
||||
PipeOp|VariableOp<CreateOp>|NamespaceOp|ProjectionDefOp|ProjectionOp|ExtractedAttributeOp|
|
||||
DeferOp|DeferSecondaryBlockOp|DeferOnOp|ExtractedMessageOp|I18nOp|I18nStartOp|I18nEndOp;
|
||||
export type CreateOp = ListEndOp<CreateOp>|StatementOp<CreateOp>|ElementOp|ElementStartOp|
|
||||
ElementEndOp|ContainerOp|ContainerStartOp|ContainerEndOp|TemplateOp|EnableBindingsOp|
|
||||
DisableBindingsOp|TextOp|ListenerOp|PipeOp|VariableOp<CreateOp>|NamespaceOp|ProjectionDefOp|
|
||||
ProjectionOp|ExtractedAttributeOp|DeferOp|DeferSecondaryBlockOp|DeferOnOp|ExtractedMessageOp|
|
||||
I18nOp|I18nStartOp|I18nEndOp|BindingSignalPlaceholder|PropertyCreateOp;
|
||||
|
||||
/**
|
||||
* An operation representing the creation of an element or container.
|
||||
@@ -775,6 +776,103 @@ export function createI18nEndOp(xref: XrefId): I18nEndOp {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* A placeholder operation reserving space for `Binding` operations that end up being
|
||||
* moved into the creation block phase for signal components.
|
||||
*
|
||||
* e.g. `Property` is moved into `PropertyCreate` from the update block to the
|
||||
* creation block.
|
||||
*/
|
||||
export interface BindingSignalPlaceholder extends Op<CreateOp> {
|
||||
kind: OpKind.BindingSignalPlaceholder;
|
||||
|
||||
/** Xref ID for the binding. */
|
||||
bindingXref: XrefId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `BindingSignalPlaceholder`.
|
||||
*/
|
||||
export function createBindingSignalPlaceholderOp(bindingXref: XrefId): BindingSignalPlaceholder {
|
||||
return {
|
||||
kind: OpKind.BindingSignalPlaceholder,
|
||||
bindingXref,
|
||||
...NEW_OP,
|
||||
};
|
||||
}
|
||||
|
||||
export interface PropertyCreateOp extends Op<CreateOp>, ConsumesSlotOpTrait, ConsumesVarsTrait {
|
||||
kind: OpKind.PropertyCreate;
|
||||
|
||||
/** Xref ID of the binding. Needed as we consume a slot for the expression to be stored. */
|
||||
xref: XrefId;
|
||||
|
||||
/**
|
||||
* Reference to the element on which the property is bound.
|
||||
*/
|
||||
target: XrefId;
|
||||
|
||||
/**
|
||||
* Name of the bound property.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Expression which is bound to the property.
|
||||
*
|
||||
* Note: Unlike `PropertyOp`, this does not accept `ir.Interpolation` as we
|
||||
* expect interpolations to be transformed into interpolation template expressions.
|
||||
*/
|
||||
expression: o.Expression;
|
||||
|
||||
/**
|
||||
* Whether this property is an animation trigger.
|
||||
*/
|
||||
isAnimationTrigger: boolean;
|
||||
|
||||
/**
|
||||
* The security context of the binding.
|
||||
*/
|
||||
securityContext: SecurityContext;
|
||||
|
||||
/**
|
||||
* The sanitizer for this property.
|
||||
*/
|
||||
sanitizer: o.Expression|null;
|
||||
|
||||
/**
|
||||
* Whether this binding is on a template.
|
||||
*/
|
||||
isTemplate: boolean;
|
||||
|
||||
/** Source span describing the property binding. */
|
||||
sourceSpan: ParseSourceSpan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `PropertyCreateOp`.
|
||||
*/
|
||||
export function createPropertyCreateOp(
|
||||
bindingXref: XrefId, target: XrefId, name: string, expression: o.Expression,
|
||||
isAnimationTrigger: boolean, securityContext: SecurityContext, isTemplate: boolean,
|
||||
sourceSpan: ParseSourceSpan): PropertyCreateOp {
|
||||
return {
|
||||
kind: OpKind.PropertyCreate,
|
||||
xref: bindingXref,
|
||||
target,
|
||||
name,
|
||||
expression,
|
||||
isAnimationTrigger,
|
||||
securityContext,
|
||||
sanitizer: null,
|
||||
isTemplate,
|
||||
sourceSpan,
|
||||
...TRAIT_CONSUMES_SLOT,
|
||||
...TRAIT_CONSUMES_VARS,
|
||||
...NEW_OP,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An index into the `consts` array which is shared across the compilation of all views in a
|
||||
* component.
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
import * as o from '../../../../../../src/output/output_ast';
|
||||
import {ParseSourceSpan} from '../../../../../../src/parse_util';
|
||||
import {OpKind} from '../enums';
|
||||
import {Interpolation} from '../interpolation';
|
||||
import {Op} from '../operations';
|
||||
import {ConsumesVarsTrait, TRAIT_CONSUMES_VARS} from '../traits';
|
||||
|
||||
import {NEW_OP} from './shared';
|
||||
|
||||
import type {Interpolation, UpdateOp} from './update';
|
||||
import type {UpdateOp} from './update';
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,18 +68,25 @@ export interface VariableOp<OpT extends Op<OpT>> extends Op<OpT> {
|
||||
* Expression representing the value of the variable.
|
||||
*/
|
||||
initializer: o.Expression;
|
||||
|
||||
/**
|
||||
* Whether the variable created is a constant.
|
||||
*/
|
||||
isConstant: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `VariableOp`.
|
||||
*/
|
||||
export function createVariableOp<OpT extends Op<OpT>>(
|
||||
xref: XrefId, variable: SemanticVariable, initializer: o.Expression): VariableOp<OpT> {
|
||||
xref: XrefId, variable: SemanticVariable, initializer: o.Expression,
|
||||
isConstant: boolean): VariableOp<OpT> {
|
||||
return {
|
||||
kind: OpKind.Variable,
|
||||
xref,
|
||||
variable,
|
||||
initializer,
|
||||
isConstant,
|
||||
...NEW_OP,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as o from '../../../../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../../../../parse_util';
|
||||
import {BindingKind, OpKind} from '../enums';
|
||||
import type {ConditionalCaseExpr} from '../expression';
|
||||
import {Interpolation} from '../interpolation';
|
||||
import {Op, XrefId} from '../operations';
|
||||
import {ConsumesVarsTrait, DependsOnSlotContextOpTrait, TRAIT_CONSUMES_VARS, TRAIT_DEPENDS_ON_SLOT_CONTEXT, TRAIT_USES_SLOT_INDEX, UsesSlotIndexTrait} from '../traits';
|
||||
|
||||
@@ -72,10 +73,6 @@ export function createInterpolateTextOp(
|
||||
};
|
||||
}
|
||||
|
||||
export class Interpolation {
|
||||
constructor(readonly strings: string[], readonly expressions: o.Expression[]) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* An intermediate binding op, that has not yet been processed into an individual property,
|
||||
* attribute, style, etc.
|
||||
@@ -83,6 +80,9 @@ export class Interpolation {
|
||||
export interface BindingOp extends Op<UpdateOp> {
|
||||
kind: OpKind.Binding;
|
||||
|
||||
/** Xref ID for the binding. */
|
||||
bindingXref: XrefId;
|
||||
|
||||
/**
|
||||
* Reference to the element on which the property is bound.
|
||||
*/
|
||||
@@ -132,11 +132,12 @@ export interface BindingOp extends Op<UpdateOp> {
|
||||
* Create a `BindingOp`, not yet transformed into a particular type of binding.
|
||||
*/
|
||||
export function createBindingOp(
|
||||
target: XrefId, kind: BindingKind, name: string, expression: o.Expression|Interpolation,
|
||||
unit: string|null, securityContext: SecurityContext, isTextAttribute: boolean,
|
||||
isTemplate: boolean, sourceSpan: ParseSourceSpan): BindingOp {
|
||||
bindingXref: XrefId, target: XrefId, kind: BindingKind, name: string,
|
||||
expression: o.Expression|Interpolation, unit: string|null, securityContext: SecurityContext,
|
||||
isTextAttribute: boolean, isTemplate: boolean, sourceSpan: ParseSourceSpan): BindingOp {
|
||||
return {
|
||||
kind: OpKind.Binding,
|
||||
bindingXref,
|
||||
bindingKind: kind,
|
||||
target,
|
||||
name,
|
||||
@@ -156,6 +157,9 @@ export function createBindingOp(
|
||||
export interface PropertyOp extends Op<UpdateOp>, ConsumesVarsTrait, DependsOnSlotContextOpTrait {
|
||||
kind: OpKind.Property;
|
||||
|
||||
/** Xref ID for the binding. Used to reference the `BindingSignalPlaceholder`. */
|
||||
bindingXref: XrefId;
|
||||
|
||||
/**
|
||||
* Reference to the element on which the property is bound.
|
||||
*/
|
||||
@@ -198,12 +202,13 @@ export interface PropertyOp extends Op<UpdateOp>, ConsumesVarsTrait, DependsOnSl
|
||||
* Create a `PropertyOp`.
|
||||
*/
|
||||
export function createPropertyOp(
|
||||
target: XrefId, name: string, expression: o.Expression|Interpolation,
|
||||
bindingXref: XrefId, target: XrefId, name: string, expression: o.Expression|Interpolation,
|
||||
isAnimationTrigger: boolean, securityContext: SecurityContext, isTemplate: boolean,
|
||||
|
||||
sourceSpan: ParseSourceSpan): PropertyOp {
|
||||
return {
|
||||
kind: OpKind.Property,
|
||||
bindingXref,
|
||||
target,
|
||||
name,
|
||||
expression,
|
||||
|
||||
@@ -51,6 +51,12 @@ export interface IdentifierVariable extends SemanticVariableBase {
|
||||
* The identifier whose value in the template is tracked in this variable.
|
||||
*/
|
||||
identifier: string;
|
||||
|
||||
/**
|
||||
* The target in the template associated with this identifier (or `null` if no such target
|
||||
* exists).
|
||||
*/
|
||||
target: XrefId|null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@ export enum CompilationJobKind {
|
||||
*/
|
||||
export abstract class CompilationJob {
|
||||
constructor(
|
||||
readonly componentName: string, readonly pool: ConstantPool,
|
||||
readonly componentName: string, readonly isSignal: boolean, readonly pool: ConstantPool,
|
||||
readonly compatibility: ir.CompatibilityMode) {}
|
||||
|
||||
kind: CompilationJobKind = CompilationJobKind.Both;
|
||||
@@ -63,9 +63,10 @@ export abstract class CompilationJob {
|
||||
*/
|
||||
export class ComponentCompilationJob extends CompilationJob {
|
||||
constructor(
|
||||
componentName: string, pool: ConstantPool, compatibility: ir.CompatibilityMode,
|
||||
readonly relativeContextFilePath: string, readonly i18nUseExternalIds: boolean) {
|
||||
super(componentName, pool, compatibility);
|
||||
componentName: string, isSignal: boolean, pool: ConstantPool,
|
||||
compatibility: ir.CompatibilityMode, readonly relativeContextFilePath: string,
|
||||
readonly i18nUseExternalIds: boolean) {
|
||||
super(componentName, isSignal, pool, compatibility);
|
||||
this.root = new ViewCompilationUnit(this, this.allocateXrefId(), null);
|
||||
this.views.set(this.root.xref, this.root);
|
||||
}
|
||||
@@ -213,8 +214,10 @@ export class ViewCompilationUnit extends CompilationUnit {
|
||||
* Compilation-in-progress of a host binding, which contains a single unit for that host binding.
|
||||
*/
|
||||
export class HostBindingCompilationJob extends CompilationJob {
|
||||
constructor(componentName: string, pool: ConstantPool, compatibility: ir.CompatibilityMode) {
|
||||
super(componentName, pool, compatibility);
|
||||
constructor(
|
||||
componentName: string, isSignal: boolean, pool: ConstantPool,
|
||||
compatibility: ir.CompatibilityMode) {
|
||||
super(componentName, isSignal, pool, compatibility);
|
||||
this.root = new HostBindingCompilationUnit(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {ConstantPool} from '../../../constant_pool';
|
||||
import * as ir from '../ir';
|
||||
|
||||
import {CompilationJob, CompilationJobKind as Kind, type ComponentCompilationJob, type HostBindingCompilationJob, type ViewCompilationUnit} from './compilation';
|
||||
|
||||
import {phaseAlignPipeVariadicVarOffset} from './phases/align_pipe_variadic_var_offset';
|
||||
import {phaseFindAnyCasts} from './phases/any_cast';
|
||||
import {phaseApplyI18nExpressions} from './phases/apply_i18n_expressions';
|
||||
@@ -21,6 +20,7 @@ import {phaseBindingSpecialization} from './phases/binding_specialization';
|
||||
import {phaseChaining} from './phases/chaining';
|
||||
import {phaseConditionals} from './phases/conditionals';
|
||||
import {phaseConstCollection} from './phases/const_collection';
|
||||
import {phaseCreationVarColocation} from './phases/creation_var_colocation';
|
||||
import {phaseEmptyElements} from './phases/empty_elements';
|
||||
import {phaseExpandSafeReads} from './phases/expand_safe_reads';
|
||||
import {phaseGenerateAdvance} from './phases/generate_advance';
|
||||
@@ -55,6 +55,7 @@ import {phaseResolveI18nPlaceholders} from './phases/resolve_i18n_placeholders';
|
||||
import {phaseResolveNames} from './phases/resolve_names';
|
||||
import {phaseResolveSanitizers} from './phases/resolve_sanitizers';
|
||||
import {phaseSaveRestoreView} from './phases/save_restore_view';
|
||||
import {phaseSignalBindings} from './phases/signal_bindings';
|
||||
import {phaseSlotAllocation} from './phases/slot_allocation';
|
||||
import {phaseStyleBindingSpecialization} from './phases/style_binding_specialization';
|
||||
import {phaseTemporaryVariables} from './phases/temporary_variables';
|
||||
@@ -78,10 +79,11 @@ const phases: Phase[] = [
|
||||
{kind: Kind.Tmpl, fn: phaseNamespace},
|
||||
{kind: Kind.Both, fn: phaseStyleBindingSpecialization},
|
||||
{kind: Kind.Both, fn: phaseBindingSpecialization},
|
||||
{kind: Kind.Tmpl, fn: phasePropagateI18nBlocks},
|
||||
{kind: Kind.Both, fn: phaseSignalBindings},
|
||||
{kind: Kind.Both, fn: phaseAttributeExtraction},
|
||||
{kind: Kind.Both, fn: phaseParseExtractedStyles},
|
||||
{kind: Kind.Tmpl, fn: phaseRemoveEmptyBindings},
|
||||
{kind: Kind.Tmpl, fn: phasePropagateI18nBlocks},
|
||||
{kind: Kind.Tmpl, fn: phaseConditionals},
|
||||
{kind: Kind.Tmpl, fn: phasePipeCreation},
|
||||
{kind: Kind.Tmpl, fn: phaseI18nTextExtraction},
|
||||
@@ -111,6 +113,7 @@ const phases: Phase[] = [
|
||||
{kind: Kind.Both, fn: phaseVarCounting},
|
||||
{kind: Kind.Tmpl, fn: phaseGenerateAdvance},
|
||||
{kind: Kind.Both, fn: phaseVariableOptimization},
|
||||
{kind: Kind.Both, fn: phaseCreationVarColocation},
|
||||
{kind: Kind.Both, fn: phaseNaming},
|
||||
{kind: Kind.Tmpl, fn: phaseMergeNextContext},
|
||||
{kind: Kind.Tmpl, fn: phaseNgContainer},
|
||||
|
||||
@@ -14,10 +14,9 @@ import {splitNsName} from '../../../ml_parser/tags';
|
||||
import * as o from '../../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../../parse_util';
|
||||
import * as t from '../../../render3/r3_ast';
|
||||
import {BindingParser} from '../../../template_parser/binding_parser';
|
||||
import * as ir from '../ir';
|
||||
|
||||
import {ComponentCompilationJob, HostBindingCompilationJob, type CompilationJob, type ViewCompilationUnit} from './compilation';
|
||||
import {type CompilationJob, ComponentCompilationJob, HostBindingCompilationJob, type ViewCompilationUnit} from './compilation';
|
||||
import {BINARY_OPERATORS, namespaceForKey} from './conversion';
|
||||
|
||||
const compatibilityMode = ir.CompatibilityMode.TemplateDefinitionBuilder;
|
||||
@@ -28,15 +27,17 @@ const compatibilityMode = ir.CompatibilityMode.TemplateDefinitionBuilder;
|
||||
* TODO: Refactor more of the ingestion code into phases.
|
||||
*/
|
||||
export function ingestComponent(
|
||||
componentName: string, template: t.Node[], constantPool: ConstantPool,
|
||||
componentName: string, isSignal: boolean, template: t.Node[], constantPool: ConstantPool,
|
||||
relativeContextFilePath: string, i18nUseExternalIds: boolean): ComponentCompilationJob {
|
||||
const cpl = new ComponentCompilationJob(
|
||||
componentName, constantPool, compatibilityMode, relativeContextFilePath, i18nUseExternalIds);
|
||||
componentName, isSignal, constantPool, compatibilityMode, relativeContextFilePath,
|
||||
i18nUseExternalIds);
|
||||
ingestNodes(cpl.root, template);
|
||||
return cpl;
|
||||
}
|
||||
|
||||
export interface HostBindingInput {
|
||||
isSignal: boolean;
|
||||
componentName: string;
|
||||
properties: e.ParsedProperty[]|null;
|
||||
attributes: {[key: string]: o.Expression};
|
||||
@@ -48,9 +49,9 @@ export interface HostBindingInput {
|
||||
* representation.
|
||||
*/
|
||||
export function ingestHostBinding(
|
||||
input: HostBindingInput, bindingParser: BindingParser,
|
||||
constantPool: ConstantPool): HostBindingCompilationJob {
|
||||
const job = new HostBindingCompilationJob(input.componentName, constantPool, compatibilityMode);
|
||||
input: HostBindingInput, constantPool: ConstantPool): HostBindingCompilationJob {
|
||||
const job = new HostBindingCompilationJob(
|
||||
input.componentName, input.isSignal, constantPool, compatibilityMode);
|
||||
for (const property of input.properties ?? []) {
|
||||
ingestHostProperty(job, property, false);
|
||||
}
|
||||
@@ -84,8 +85,11 @@ export function ingestHostProperty(
|
||||
if (property.isAnimation) {
|
||||
bindingKind = ir.BindingKind.Animation;
|
||||
}
|
||||
|
||||
const bindingXref = job.allocateXrefId();
|
||||
job.root.create.push(ir.createBindingSignalPlaceholderOp(bindingXref));
|
||||
job.root.update.push(ir.createBindingOp(
|
||||
job.root.xref, bindingKind, property.name, expression, null,
|
||||
bindingXref, job.root.xref, bindingKind, property.name, expression, null,
|
||||
SecurityContext
|
||||
.NONE /* TODO: what should we pass as security context? Passing NONE for now. */,
|
||||
isTextAttribute, false, property.sourceSpan));
|
||||
@@ -93,8 +97,10 @@ export function ingestHostProperty(
|
||||
|
||||
export function ingestHostAttribute(
|
||||
job: HostBindingCompilationJob, name: string, value: o.Expression): void {
|
||||
const bindingXref = job.allocateXrefId();
|
||||
const attrBinding = ir.createBindingOp(
|
||||
job.root.xref, ir.BindingKind.Attribute, name, value, null, SecurityContext.NONE, true, false,
|
||||
bindingXref, job.root.xref, ir.BindingKind.Attribute, name, value, null, SecurityContext.NONE,
|
||||
true, false,
|
||||
/* TODO: host attribute source spans */ null!);
|
||||
job.root.update.push(attrBinding);
|
||||
}
|
||||
@@ -611,7 +617,7 @@ enum BindingFlags {
|
||||
}
|
||||
|
||||
function ingestBinding(
|
||||
view: ViewCompilationUnit, xref: ir.XrefId, name: string, value: e.AST|o.Expression,
|
||||
view: ViewCompilationUnit, targetXref: ir.XrefId, name: string, value: e.AST|o.Expression,
|
||||
type: e.BindingType, unit: string|null, securityContext: SecurityContext,
|
||||
sourceSpan: ParseSourceSpan, flags: BindingFlags): void {
|
||||
if (value instanceof e.ASTWithSource) {
|
||||
@@ -622,7 +628,8 @@ function ingestBinding(
|
||||
type === e.BindingType.Property) {
|
||||
// This binding only exists for later const extraction, and is not an actual binding to be
|
||||
// created.
|
||||
view.create.push(ir.createExtractedAttributeOp(xref, ir.BindingKind.Property, name, null));
|
||||
view.create.push(
|
||||
ir.createExtractedAttributeOp(targetXref, ir.BindingKind.Property, name, null));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -639,9 +646,13 @@ function ingestBinding(
|
||||
}
|
||||
|
||||
const kind: ir.BindingKind = BINDING_KINDS.get(type)!;
|
||||
const bindingXref = view.job.allocateXrefId();
|
||||
|
||||
view.create.push(ir.createBindingSignalPlaceholderOp(bindingXref));
|
||||
view.update.push(ir.createBindingOp(
|
||||
xref, kind, name, expression, unit, securityContext, !!(flags & BindingFlags.TextValue),
|
||||
!!(flags & BindingFlags.IsStructuralTemplateAttribute), sourceSpan));
|
||||
bindingXref, targetXref, kind, name, expression, unit, securityContext,
|
||||
!!(flags & BindingFlags.TextValue), !!(flags & BindingFlags.IsStructuralTemplateAttribute),
|
||||
sourceSpan));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -151,6 +151,12 @@ export function reference(slot: number): o.Expression {
|
||||
]);
|
||||
}
|
||||
|
||||
export function shallowReference(slot: number): o.Expression {
|
||||
return o.importExpr(Identifiers.shallowReference).callFn([
|
||||
o.literal(slot),
|
||||
]);
|
||||
}
|
||||
|
||||
export function nextContext(steps: number): o.Expression {
|
||||
return o.importExpr(Identifiers.nextContext).callFn(steps === 1 ? [] : [o.literal(steps)]);
|
||||
}
|
||||
@@ -287,6 +293,22 @@ export function classMap(expression: o.Expression, sourceSpan: ParseSourceSpan):
|
||||
return call(Identifiers.classMap, [expression], sourceSpan);
|
||||
}
|
||||
|
||||
export function propertyCreate(
|
||||
slot: number, name: string, expression: o.Expression, sanitizer: o.Expression|null,
|
||||
sourceSpan: ParseSourceSpan): ir.CreateOp {
|
||||
const args: o.Expression[] = [
|
||||
o.literal(slot),
|
||||
o.literal(name),
|
||||
o.fn([], [new o.ReturnStatement(expression)]),
|
||||
];
|
||||
|
||||
if (sanitizer !== null) {
|
||||
args.push(sanitizer);
|
||||
}
|
||||
|
||||
return call(Identifiers.propertyCreate, args, sourceSpan);
|
||||
}
|
||||
|
||||
const PIPE_BINDINGS: o.ExternalReference[] = [
|
||||
Identifiers.pipeBind1,
|
||||
Identifiers.pipeBind2,
|
||||
@@ -424,6 +446,13 @@ export function pureFunction(
|
||||
);
|
||||
}
|
||||
|
||||
export function stringifyInterpolation(
|
||||
staticParts: string[], expressions: o.Expression[]): o.Expression {
|
||||
return o.taggedTemplate(
|
||||
o.importExpr(Identifiers.stringifyInterpolation),
|
||||
new o.TemplateLiteral(staticParts.map(p => new o.TemplateLiteralElement(p)), expressions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collates the string an expression arguments for an interpolation instruction.
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@ export function phaseAttributeExtraction(job: CompilationJob): void {
|
||||
extractAttributeOp(unit, op, elements);
|
||||
break;
|
||||
case ir.OpKind.Property:
|
||||
case ir.OpKind.PropertyCreate:
|
||||
if (!op.isAnimationTrigger) {
|
||||
ir.OpList.insertBefore<ir.CreateOp>(
|
||||
ir.createExtractedAttributeOp(
|
||||
|
||||
@@ -64,8 +64,9 @@ export function phaseBindingSpecialization(job: CompilationJob): void {
|
||||
ir.OpList.replace<ir.UpdateOp>(
|
||||
op,
|
||||
ir.createPropertyOp(
|
||||
op.target, op.name, op.expression, op.bindingKind === ir.BindingKind.Animation,
|
||||
op.securityContext, op.isTemplate, op.sourceSpan));
|
||||
op.bindingXref, op.target, op.name, op.expression,
|
||||
op.bindingKind === ir.BindingKind.Animation, op.securityContext, op.isTemplate,
|
||||
op.sourceSpan));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -25,6 +25,7 @@ const CHAINABLE = new Set([
|
||||
R3.listener,
|
||||
R3.listener,
|
||||
R3.property,
|
||||
R3.propertyCreate,
|
||||
R3.styleProp,
|
||||
R3.stylePropInterpolate1,
|
||||
R3.stylePropInterpolate2,
|
||||
@@ -119,7 +120,8 @@ interface Chain {
|
||||
* The expression representing the whole current chained call.
|
||||
*
|
||||
* This should be the same as `op.statement.expression`, but is extracted here for convenience
|
||||
* since the `op` type doesn't capture the fact that `op.statement` is an `o.ExpressionStatement`.
|
||||
* since the `op` type doesn't capture the fact that `op.statement` is an
|
||||
* `o.ExpressionStatement`.
|
||||
*/
|
||||
expression: o.Expression;
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 * as o from '../../../../output/output_ast';
|
||||
import * as ir from '../../ir';
|
||||
import type {CompilationJob, CompilationUnit} from '../compilation';
|
||||
|
||||
/**
|
||||
* Moves variables defined in creation mode to only be initialized after their creation, splitting
|
||||
* declaration and initialization if necessary to allow forward references.
|
||||
*/
|
||||
export function phaseCreationVarColocation(cpl: CompilationJob): void {
|
||||
for (const unit of cpl.units) {
|
||||
processUnit(unit);
|
||||
}
|
||||
}
|
||||
|
||||
function processUnit(unit: CompilationUnit): void {
|
||||
const shallowDeclarations = new Map<ir.XrefId, ir.VariableOp<ir.CreateOp>[]>();
|
||||
const seenVariableReads = new Set<ir.XrefId>;
|
||||
let shallow = true;
|
||||
for (const op of unit.create) {
|
||||
if (shallow && op.kind === ir.OpKind.Variable &&
|
||||
op.variable.kind === ir.SemanticVariableKind.Identifier && op.variable.target !== null) {
|
||||
// This variable represents the identity of an entity within the current view (shallow) which
|
||||
// may or may not have been created yet.
|
||||
const target = op.variable.target;
|
||||
if (!shallowDeclarations.has(target)) {
|
||||
shallowDeclarations.set(target, []);
|
||||
}
|
||||
shallowDeclarations.get(target)!.push(op);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scan expressions in this operation for both `NextContext` operations (which affect whether
|
||||
// seen reference declarations are shallow or not) or for `ReferenceExpr`s which may represent
|
||||
// forward references that later need to be accounted for.
|
||||
ir.visitExpressionsInOp(op, (exp, flags) => {
|
||||
if (flags & ir.VisitorContextFlag.InChildOperation) {
|
||||
return;
|
||||
}
|
||||
if (exp instanceof ir.NextContextExpr) {
|
||||
shallow = false;
|
||||
} else if (exp instanceof ir.ReadVariableExpr) {
|
||||
seenVariableReads.add(exp.xref);
|
||||
}
|
||||
});
|
||||
|
||||
if (ir.hasConsumesSlotTrait(op) && shallowDeclarations.has(op.xref)) {
|
||||
// `op` is creating an entity for which at least one shallow declaration has previously been
|
||||
// established. If nothing has referenced those declarations, it can be moved to follow `op`.
|
||||
// Otherwise, we can split the variable into its declaration and assignment.
|
||||
for (const declOp of shallowDeclarations.get(op.xref)!) {
|
||||
// Within the variable initializer, convert the `ReferenceExpr` into a
|
||||
// `ShallowReferenceExpr`. This is necessary since we might be moving the initializer past a
|
||||
// `NextContext` call.
|
||||
declOp.initializer = ir.transformExpressionsInExpression(declOp.initializer, expr => {
|
||||
if (!(expr instanceof ir.ReferenceExpr)) {
|
||||
return expr;
|
||||
}
|
||||
|
||||
const shallowExpr = new ir.ShallowReferenceExpr(expr.target, expr.offset);
|
||||
shallowExpr.targetSlot = expr.targetSlot;
|
||||
return shallowExpr;
|
||||
}, ir.VisitorContextFlag.None);
|
||||
|
||||
if (!seenVariableReads.has(declOp.xref)) {
|
||||
// No references have been recorded to this variable, so move it to follow this
|
||||
// declaration.
|
||||
ir.OpList.remove<ir.CreateOp>(declOp);
|
||||
ir.OpList.insertAfter<ir.CreateOp>(declOp, op);
|
||||
} else {
|
||||
// A forward reference has been observed, so leave the existing declaration in place as an
|
||||
// initializer to `undefined`, and set the variable to its value after its declaration.
|
||||
const initializer = declOp.initializer;
|
||||
declOp.initializer = o.literal(undefined);
|
||||
declOp.isConstant = false;
|
||||
|
||||
const readVar = new ir.ReadVariableExpr(declOp.xref);
|
||||
// TODO: variable naming should run after this and take care of this for us.
|
||||
readVar.name = declOp.variable.name;
|
||||
const assignment =
|
||||
new o.BinaryOperatorExpr(o.BinaryOperator.Assign, readVar, initializer);
|
||||
ir.OpList.insertAfter<ir.CreateOp>(ir.createStatementOp(assignment.toStmt()), op);
|
||||
}
|
||||
}
|
||||
shallowDeclarations.delete(op.xref);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import * as o from '../../../../output/output_ast';
|
||||
import * as ir from '../../ir';
|
||||
|
||||
import type {ComponentCompilationJob, ViewCompilationUnit} from '../compilation';
|
||||
|
||||
/**
|
||||
@@ -53,9 +52,9 @@ function recursivelyProcessView(view: ViewCompilationUnit, parentScope: Scope|nu
|
||||
}
|
||||
}
|
||||
|
||||
// Prepend the declarations for all available variables in scope to the `update` block.
|
||||
const preambleOps = generateVariablesInScopeForView(view, scope);
|
||||
view.update.prepend(preambleOps);
|
||||
// Prepend the declarations for all available variables in scope to both blocks.
|
||||
view.create.prepend(generateVariablesInScopeForView(view, scope));
|
||||
view.update.prepend(generateVariablesInScopeForView(view, scope));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,6 +130,7 @@ function getScopeForView(view: ViewCompilationUnit, parent: Scope|null): Scope {
|
||||
kind: ir.SemanticVariableKind.Identifier,
|
||||
name: null,
|
||||
identifier,
|
||||
target: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ function getScopeForView(view: ViewCompilationUnit, parent: Scope|null): Scope {
|
||||
kind: ir.SemanticVariableKind.Identifier,
|
||||
name: null,
|
||||
identifier: op.localRefs[offset].name,
|
||||
target: op.xref,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -169,16 +170,17 @@ function getScopeForView(view: ViewCompilationUnit, parent: Scope|null): Scope {
|
||||
* This is a recursive process, as views inherit variables available from their parent view, which
|
||||
* itself may have inherited variables, etc.
|
||||
*/
|
||||
function generateVariablesInScopeForView(
|
||||
view: ViewCompilationUnit, scope: Scope): ir.VariableOp<ir.UpdateOp>[] {
|
||||
const newOps: ir.VariableOp<ir.UpdateOp>[] = [];
|
||||
function generateVariablesInScopeForView<OpT extends ir.Op<OpT>>(
|
||||
view: ViewCompilationUnit, scope: Scope): ir.VariableOp<OpT>[] {
|
||||
const newOps: ir.VariableOp<OpT>[] = [];
|
||||
|
||||
if (scope.view !== view.xref) {
|
||||
// Before generating variables for a parent view, we need to switch to the context of the parent
|
||||
// view with a `nextContext` expression. This context switching operation itself declares a
|
||||
// variable, because the context of the view may be referenced directly.
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.job.allocateXrefId(), scope.viewContextVariable, new ir.NextContextExpr()));
|
||||
view.job.allocateXrefId(), scope.viewContextVariable, new ir.NextContextExpr(),
|
||||
/* isConstant */ true));
|
||||
}
|
||||
|
||||
// Add variables for all context variables available in this scope's view.
|
||||
@@ -188,18 +190,20 @@ function generateVariablesInScopeForView(
|
||||
const variable = value === ir.CTX_REF ? context : new o.ReadPropExpr(context, value);
|
||||
// Add the variable declaration.
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.job.allocateXrefId(), scope.contextVariables.get(name)!, variable));
|
||||
view.job.allocateXrefId(), scope.contextVariables.get(name)!, variable,
|
||||
/* isConstant */ true));
|
||||
}
|
||||
|
||||
// Add variables for all local references declared for elements in this scope.
|
||||
for (const ref of scope.references) {
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.job.allocateXrefId(), ref.variable, new ir.ReferenceExpr(ref.targetId, ref.offset)));
|
||||
view.job.allocateXrefId(), ref.variable, new ir.ReferenceExpr(ref.targetId, ref.offset),
|
||||
/* isConstant */ true));
|
||||
}
|
||||
|
||||
if (scope.parent !== null) {
|
||||
// Recursively add variables from the parent scope.
|
||||
newOps.push(...generateVariablesInScopeForView(view, scope.parent));
|
||||
newOps.push(...generateVariablesInScopeForView<OpT>(view, scope.parent));
|
||||
}
|
||||
return newOps;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {sanitizeIdentifier} from '../../../../parse_util';
|
||||
import {hyphenate} from '../../../../render3/view/style_parser';
|
||||
import * as ir from '../../ir';
|
||||
import {ViewCompilationUnit, type CompilationJob, type CompilationUnit, HostBindingCompilationUnit} from '../compilation';
|
||||
import {type CompilationJob, type CompilationUnit, HostBindingCompilationUnit, ViewCompilationUnit} from '../compilation';
|
||||
import {prefixWithNamespace} from '../conversion';
|
||||
|
||||
/**
|
||||
@@ -37,6 +37,7 @@ function addNamesToView(
|
||||
for (const op of unit.ops()) {
|
||||
switch (op.kind) {
|
||||
case ir.OpKind.Property:
|
||||
case ir.OpKind.PropertyCreate:
|
||||
case ir.OpKind.HostProperty:
|
||||
if (op.isAnimationTrigger) {
|
||||
op.name = '@' + op.name;
|
||||
|
||||
@@ -26,6 +26,9 @@ interface Rule<T extends ir.CreateOp|ir.UpdateOp> {
|
||||
const CREATE_ORDERING: Array<Rule<ir.CreateOp>> = [
|
||||
{test: op => op.kind === ir.OpKind.Listener && op.hostListener && op.isAnimationListener},
|
||||
{test: op => op.kind === ir.OpKind.Listener && !(op.hostListener && op.isAnimationListener)},
|
||||
|
||||
// TODO(signals): Think about ordering for instructions in the create block!
|
||||
// Right now, not relevant as we only have a single property create instruction.
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -47,8 +50,14 @@ const UPDATE_ORDERING: Array<Rule<ir.UpdateOp>> = [
|
||||
* The set of all op kinds we handle in the reordering phase.
|
||||
*/
|
||||
const handledOpKinds = new Set([
|
||||
ir.OpKind.Listener, ir.OpKind.StyleMap, ir.OpKind.ClassMap, ir.OpKind.StyleProp,
|
||||
ir.OpKind.ClassProp, ir.OpKind.Property, ir.OpKind.HostProperty, ir.OpKind.Attribute
|
||||
ir.OpKind.Listener,
|
||||
ir.OpKind.StyleMap,
|
||||
ir.OpKind.ClassMap,
|
||||
ir.OpKind.StyleProp,
|
||||
ir.OpKind.ClassProp,
|
||||
ir.OpKind.Property,
|
||||
ir.OpKind.HostProperty,
|
||||
ir.OpKind.Attribute,
|
||||
]);
|
||||
|
||||
export function phaseOrdering(job: CompilationJob) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import * as o from '../../../../output/output_ast';
|
||||
import {Identifiers} from '../../../../render3/r3_identifiers';
|
||||
import * as ir from '../../ir';
|
||||
import {ViewCompilationUnit, type CompilationJob, type CompilationUnit} from '../compilation';
|
||||
import {type CompilationJob, type CompilationUnit, ViewCompilationUnit} from '../compilation';
|
||||
import * as ng from '../instruction';
|
||||
|
||||
/**
|
||||
@@ -122,10 +122,17 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList<ir.CreateOp
|
||||
if (op.variable.name === null) {
|
||||
throw new Error(`AssertionError: unnamed variable ${op.xref}`);
|
||||
}
|
||||
// Optimization for variable co-location. If we know the initializer is undefined,
|
||||
// we don't need to set an explicit one to avoid e.g. a `let b = undefined`.
|
||||
let initializer: o.Expression|undefined = op.initializer;
|
||||
if (initializer instanceof o.LiteralExpr && initializer.value === undefined) {
|
||||
initializer = undefined;
|
||||
}
|
||||
ir.OpList.replace<ir.CreateOp>(
|
||||
op,
|
||||
ir.createStatementOp(new o.DeclareVarStmt(
|
||||
op.variable.name, op.initializer, undefined, o.StmtModifier.Final)));
|
||||
op.variable.name, initializer, undefined,
|
||||
op.isConstant ? o.StmtModifier.Final : undefined)));
|
||||
break;
|
||||
case ir.OpKind.Namespace:
|
||||
switch (op.active) {
|
||||
@@ -164,6 +171,14 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList<ir.CreateOp
|
||||
ir.OpList.replace<ir.CreateOp>(
|
||||
op, ng.projection(op.slot, op.projectionSlotIndex, op.attributes));
|
||||
break;
|
||||
case ir.OpKind.PropertyCreate:
|
||||
if (op.expression instanceof ir.Interpolation) {
|
||||
// TODO: handle interpolation
|
||||
} else {
|
||||
ir.OpList.replace(
|
||||
op, ng.propertyCreate(op.slot!, op.name, op.expression, op.sanitizer, op.sourceSpan));
|
||||
}
|
||||
break;
|
||||
case ir.OpKind.Statement:
|
||||
// Pass statement operations directly through.
|
||||
break;
|
||||
@@ -268,7 +283,8 @@ function reifyUpdateOperations(_unit: CompilationUnit, ops: ir.OpList<ir.UpdateO
|
||||
ir.OpList.replace<ir.UpdateOp>(
|
||||
op,
|
||||
ir.createStatementOp(new o.DeclareVarStmt(
|
||||
op.variable.name, op.initializer, undefined, o.StmtModifier.Final)));
|
||||
op.variable.name, op.initializer, undefined,
|
||||
op.isConstant ? o.StmtModifier.Final : undefined)));
|
||||
break;
|
||||
case ir.OpKind.Conditional:
|
||||
if (op.processed === null) {
|
||||
@@ -300,6 +316,8 @@ function reifyIrExpression(expr: o.Expression): o.Expression {
|
||||
return ng.nextContext(expr.steps);
|
||||
case ir.ExpressionKind.Reference:
|
||||
return ng.reference(expr.targetSlot! + 1 + expr.offset);
|
||||
case ir.ExpressionKind.ShallowReference:
|
||||
return ng.shallowReference(expr.targetSlot! + 1 + expr.offset);
|
||||
case ir.ExpressionKind.LexicalRead:
|
||||
throw new Error(`AssertionError: unresolved LexicalRead of ${expr.name}`);
|
||||
case ir.ExpressionKind.RestoreView:
|
||||
@@ -341,6 +359,8 @@ function reifyIrExpression(expr: o.Expression): o.Expression {
|
||||
return o.importExpr(sanitizerIdentifierMap.get(expr.fn)!);
|
||||
case ir.ExpressionKind.SlotLiteralExpr:
|
||||
return o.literal(expr.targetSlot);
|
||||
case ir.ExpressionKind.InterpolationTemplateExpr:
|
||||
return ng.stringifyInterpolation(expr.staticParts, expr.expressionParts);
|
||||
default:
|
||||
throw new Error(`AssertionError: Unsupported reification of ir.Expression kind: ${
|
||||
ir.ExpressionKind[(expr as ir.Expression).kind]}`);
|
||||
|
||||
@@ -6,23 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as o from '../../../../output/output_ast';
|
||||
import * as ir from '../../ir';
|
||||
import type {CompilationJob} from '../compilation';
|
||||
|
||||
export function phaseRemoveEmptyBindings(job: CompilationJob): void {
|
||||
for (const unit of job.units) {
|
||||
for (const op of unit.update) {
|
||||
for (const op of unit.ops()) {
|
||||
switch (op.kind) {
|
||||
case ir.OpKind.Attribute:
|
||||
case ir.OpKind.Binding:
|
||||
case ir.OpKind.ClassProp:
|
||||
case ir.OpKind.ClassMap:
|
||||
case ir.OpKind.Property:
|
||||
case ir.OpKind.PropertyCreate:
|
||||
case ir.OpKind.StyleProp:
|
||||
case ir.OpKind.StyleMap:
|
||||
if (op.expression instanceof ir.EmptyExpr) {
|
||||
ir.OpList.remove<ir.UpdateOp>(op);
|
||||
ir.OpList.remove<ir.CreateOp|ir.UpdateOp>(op);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,20 @@ function processLexicalScope(view: CompilationUnit, ops: ir.OpList<ir.CreateOp|i
|
||||
case ir.OpKind.Variable:
|
||||
switch (op.variable.kind) {
|
||||
case ir.SemanticVariableKind.Context:
|
||||
if (op.variable.view === view.xref) {
|
||||
// This variable is for the same view as `ctx`. Ideally we'd use `ctx`, but we should
|
||||
// prefer the variable in non-root views because of the risk of closure-capturing
|
||||
// `ctx`.
|
||||
|
||||
if (view === view.job.root) {
|
||||
// This is the root view, so it's safe to use `ctx` and we don't need to use this
|
||||
// variable.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This is a reference to a different context.
|
||||
scope.set(op.variable.view, new ir.ReadVariableExpr(op.xref));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ir.OpKind.Listener:
|
||||
|
||||
@@ -27,32 +27,38 @@ const sanitizers = new Map<SecurityContext, ir.SanitizerFn|null>([
|
||||
export function phaseResolveSanitizers(job: ComponentCompilationJob): void {
|
||||
for (const unit of job.units) {
|
||||
const elements = getElementsByXrefId(unit);
|
||||
let sanitizerFn: ir.SanitizerFn|null;
|
||||
for (const op of unit.update) {
|
||||
for (const op of unit.ops()) {
|
||||
switch (op.kind) {
|
||||
case ir.OpKind.Property:
|
||||
case ir.OpKind.PropertyCreate:
|
||||
case ir.OpKind.Attribute:
|
||||
sanitizerFn = sanitizers.get(op.securityContext) || null;
|
||||
op.sanitizer = sanitizerFn ? new ir.SanitizerExpr(sanitizerFn) : null;
|
||||
// If there was no sanitization function found based on the security context of an
|
||||
// attribute/property, check whether this attribute/property is one of the
|
||||
// security-sensitive <iframe> attributes (and that the current element is actually an
|
||||
// <iframe>).
|
||||
if (op.sanitizer === null) {
|
||||
const ownerOp = elements.get(op.target);
|
||||
if (ownerOp === undefined) {
|
||||
throw Error('Property should have an element-like owner');
|
||||
}
|
||||
if (isIframeElement(ownerOp) && isIframeSecuritySensitiveAttr(op.name)) {
|
||||
op.sanitizer = new ir.SanitizerExpr(ir.SanitizerFn.IframeAttribute);
|
||||
}
|
||||
}
|
||||
resolveSanitizer(op, elements);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSanitizer(
|
||||
op: ir.PropertyOp|ir.PropertyCreateOp|ir.AttributeOp,
|
||||
elements: Map<ir.XrefId, ir.ElementOrContainerOps>): void {
|
||||
const sanitizerFn = sanitizers.get(op.securityContext) || null;
|
||||
op.sanitizer = sanitizerFn ? new ir.SanitizerExpr(sanitizerFn) : null;
|
||||
// If there was no sanitization function found based on the security context of an
|
||||
// attribute/property, check whether this attribute/property is one of the
|
||||
// security-sensitive <iframe> attributes (and that the current element is actually an
|
||||
// <iframe>).
|
||||
if (op.sanitizer === null) {
|
||||
const ownerOp = elements.get(op.target);
|
||||
if (ownerOp === undefined) {
|
||||
throw Error('Property should have an element-like owner');
|
||||
}
|
||||
if (isIframeElement(ownerOp) && isIframeSecuritySensitiveAttr(op.name)) {
|
||||
op.sanitizer = new ir.SanitizerExpr(ir.SanitizerFn.IframeAttribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given op represents an iframe element.
|
||||
*/
|
||||
|
||||
@@ -19,7 +19,8 @@ export function phaseSaveRestoreView(job: ComponentCompilationJob): void {
|
||||
name: null,
|
||||
view: view.xref,
|
||||
},
|
||||
new ir.GetCurrentViewExpr()),
|
||||
new ir.GetCurrentViewExpr(), /* isConstant */ true),
|
||||
|
||||
]);
|
||||
|
||||
for (const op of view.create) {
|
||||
@@ -56,7 +57,7 @@ function addSaveRestoreViewOperationToListener(unit: ViewCompilationUnit, op: ir
|
||||
name: null,
|
||||
view: unit.xref,
|
||||
},
|
||||
new ir.RestoreViewExpr(unit.xref)),
|
||||
new ir.RestoreViewExpr(unit.xref), /* isConstant */ true),
|
||||
]);
|
||||
|
||||
// The "restore view" operation in listeners requires a call to `resetView` to reset the
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 * as ir from '../../ir';
|
||||
import {CompilationJob, CompilationUnit} from '../compilation';
|
||||
|
||||
/**
|
||||
* Phase that moves specialized `Property` operations to the creation
|
||||
* block if the compilation job targets is signal based. Also removes
|
||||
* all superfluous binding signal placeholders.
|
||||
*
|
||||
* This phase exists as we want to avoid having to the repeat binding specialization
|
||||
* logic for property bindings, but rather re-use the already-specialized information.
|
||||
*/
|
||||
export function phaseSignalBindings(cpl: CompilationJob): void {
|
||||
for (const unit of cpl.units) {
|
||||
processUnit(unit);
|
||||
}
|
||||
}
|
||||
|
||||
function processUnit(unit: CompilationUnit) {
|
||||
const placeholders = new Map<ir.XrefId, ir.BindingSignalPlaceholder>();
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.BindingSignalPlaceholder) {
|
||||
placeholders.set(op.bindingXref, op);
|
||||
}
|
||||
}
|
||||
|
||||
// For signal jobs, we move:
|
||||
// - property operations into the create block.
|
||||
if (unit.job.isSignal) {
|
||||
for (const op of unit.update) {
|
||||
if (op.kind === ir.OpKind.Property) {
|
||||
const placeholderOp = placeholders.get(op.bindingXref);
|
||||
if (placeholderOp === undefined) {
|
||||
throw new Error(
|
||||
'Expected binding placeholder operation to exist for property operation.');
|
||||
}
|
||||
|
||||
// Signal property bindings, do not maintain a separate instruction for property
|
||||
// interpolations as the expression is wrapped is `computed` anyway. Instead, we
|
||||
// generate an interpolation template literal expression.
|
||||
const expression = op.expression instanceof ir.Interpolation ?
|
||||
new ir.InterpolationTemplateExpr(op.expression.strings, op.expression.expressions) :
|
||||
op.expression;
|
||||
const createOp = ir.createPropertyCreateOp(
|
||||
op.bindingXref, op.target, op.name, expression, op.isAnimationTrigger,
|
||||
op.securityContext, op.isTemplate, op.sourceSpan);
|
||||
|
||||
// Replace the placeholder with the new instruction.
|
||||
ir.OpList.replace<ir.CreateOp>(placeholderOp, createOp);
|
||||
placeholders.delete(op.bindingXref);
|
||||
|
||||
// Remove the property update operation, as we have the create operation now.
|
||||
ir.OpList.remove<ir.UpdateOp>(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all remaining placeholders. Not all binding operations
|
||||
// end up in the create block, even for signal jobs. For non-signal
|
||||
// jobs we remove all placeholder operations.
|
||||
for (const op of placeholders.values()) {
|
||||
ir.OpList.remove<ir.CreateOp>(op);
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ function varsUsedByOp(op: (ir.CreateOp|ir.UpdateOp)&ir.ConsumesVarsTrait): numbe
|
||||
let slots: number;
|
||||
switch (op.kind) {
|
||||
case ir.OpKind.Property:
|
||||
case ir.OpKind.PropertyCreate:
|
||||
case ir.OpKind.HostProperty:
|
||||
case ir.OpKind.Attribute:
|
||||
// All of these bindings use 1 variable slot, plus 1 slot for every interpolated expression,
|
||||
|
||||
@@ -276,10 +276,15 @@ function fencesForIrExpression(expr: ir.Expression): Fence {
|
||||
function collectOpInfo(op: ir.CreateOp|ir.UpdateOp): OpInfo {
|
||||
let fences = Fence.None;
|
||||
const variablesUsed = new Set<ir.XrefId>();
|
||||
ir.visitExpressionsInOp(op, expr => {
|
||||
ir.visitExpressionsInOp(op, (expr, flags) => {
|
||||
if (!ir.isIrExpression(expr)) {
|
||||
return;
|
||||
}
|
||||
// Fences from child operations are not of interest because they allocate
|
||||
// their own variables.
|
||||
if (flags & ir.VisitorContextFlag.InChildOperation) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (expr.kind) {
|
||||
case ir.ExpressionKind.ReadVariable:
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {createComputed} from './src/computed';
|
||||
export {COMPUTED_NODE, ComputedNode, COMPUTING, createComputed, ERRORED, UNSET} from './src/computed';
|
||||
export {defaultEquals, ValueEqualityFn} from './src/equality';
|
||||
export {setThrowInvalidWriteToSignalError} from './src/errors';
|
||||
export {consumerAfterComputation, consumerBeforeComputation, consumerDestroy, getActiveConsumer, isInNotificationPhase, isReactive, producerAccessed, producerNotifyConsumers, producerUpdatesAllowed, producerUpdateValueVersion, Reactive, REACTIVE_NODE, ReactiveNode, setActiveConsumer, SIGNAL} from './src/graph';
|
||||
export {consumerAfterComputation, consumerBeforeComputation, consumerDestroy, consumerMarkDirty, getActiveConsumer, isInNotificationPhase, isReactive, producerAccessed, producerNotifyConsumers, producerUpdatesAllowed, producerUpdateValueVersion, Reactive, REACTIVE_NODE, ReactiveNode, setActiveConsumer, SIGNAL} from './src/graph';
|
||||
export {createSignal, setPostSignalSetFn, SignalGetter, signalMutateFn, SignalNode, signalSetFn, signalUpdateFn} from './src/signal';
|
||||
export {createWatch, Watch, WatchCleanupFn, WatchCleanupRegisterFn} from './src/watch';
|
||||
export {setAlternateWeakRefImpl} from './src/weak_ref';
|
||||
|
||||
@@ -68,26 +68,26 @@ export function createComputed<T>(computation: () => T): ComputedGetter<T> {
|
||||
* A dedicated symbol used before a computed value has been calculated for the first time.
|
||||
* Explicitly typed as `any` so we can use it as signal's value.
|
||||
*/
|
||||
const UNSET: any = /* @__PURE__ */ Symbol('UNSET');
|
||||
export const UNSET: any = /* @__PURE__ */ Symbol('UNSET');
|
||||
|
||||
/**
|
||||
* A dedicated symbol used in place of a computed signal value to indicate that a given computation
|
||||
* is in progress. Used to detect cycles in computation chains.
|
||||
* Explicitly typed as `any` so we can use it as signal's value.
|
||||
*/
|
||||
const COMPUTING: any = /* @__PURE__ */ Symbol('COMPUTING');
|
||||
export const COMPUTING: any = /* @__PURE__ */ Symbol('COMPUTING');
|
||||
|
||||
/**
|
||||
* A dedicated symbol used in place of a computed signal value to indicate that a given computation
|
||||
* failed. The thrown error is cached until the computation gets dirty again.
|
||||
* Explicitly typed as `any` so we can use it as signal's value.
|
||||
*/
|
||||
const ERRORED: any = /* @__PURE__ */ Symbol('ERRORED');
|
||||
export const ERRORED: any = /* @__PURE__ */ Symbol('ERRORED');
|
||||
|
||||
// Note: Using an IIFE here to ensure that the spread assignment is not considered
|
||||
// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
|
||||
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
|
||||
const COMPUTED_NODE = /* @__PURE__ */ (() => {
|
||||
export const COMPUTED_NODE = /* @__PURE__ */ (() => {
|
||||
return {
|
||||
...REACTIVE_NODE,
|
||||
value: UNSET,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* @description
|
||||
* Entry point from which you should import all public core APIs.
|
||||
*/
|
||||
|
||||
export * from './metadata';
|
||||
export * from './version';
|
||||
export {TypeDecorator} from './util/decorators';
|
||||
|
||||
@@ -36,4 +36,9 @@ export {
|
||||
export {
|
||||
assertNotInReactiveContext,
|
||||
} from './render3/reactivity/asserts';
|
||||
export {input} from './render3/reactivity/input';
|
||||
export {InputSignal, ɵɵGetInputSignalWriteType} from './render3/reactivity/input_signal';
|
||||
export {ModelSignal} from './render3/reactivity/model_signal';
|
||||
export {contentChild, contentChildren, viewChild, viewChildren} from './render3/reactivity/queries';
|
||||
export {output} from './render3/reactivity/output';
|
||||
// clang-format on
|
||||
|
||||
@@ -147,6 +147,7 @@ export {
|
||||
ɵɵprojection,
|
||||
ɵɵprojectionDef,
|
||||
ɵɵproperty,
|
||||
ɵɵpropertyCreate,
|
||||
ɵɵpropertyInterpolate,
|
||||
ɵɵpropertyInterpolate1,
|
||||
ɵɵpropertyInterpolate2,
|
||||
@@ -157,6 +158,7 @@ export {
|
||||
ɵɵpropertyInterpolate7,
|
||||
ɵɵpropertyInterpolate8,
|
||||
ɵɵpropertyInterpolateV,
|
||||
ɵɵstringifyInterpolation,
|
||||
ɵɵProvidersFeature,
|
||||
ɵɵHostDirectivesFeature,
|
||||
ɵɵpureFunction0,
|
||||
@@ -169,6 +171,8 @@ export {
|
||||
ɵɵpureFunction7,
|
||||
ɵɵpureFunction8,
|
||||
ɵɵpureFunctionV,
|
||||
ɵɵcontentQueryCreate,
|
||||
ɵɵviewQueryCreate,
|
||||
ɵɵqueryRefresh,
|
||||
ɵɵreference,
|
||||
ɵɵresetView,
|
||||
@@ -186,6 +190,7 @@ export {
|
||||
ɵɵsetNgModuleScope,
|
||||
ɵɵgetComponentDepsFactory,
|
||||
ɵɵStandaloneFeature,
|
||||
ɵɵshallowReference,
|
||||
ɵɵstyleMap,
|
||||
ɵɵstyleMapInterpolate1,
|
||||
ɵɵstyleMapInterpolate2,
|
||||
|
||||
@@ -44,7 +44,8 @@ function symbolIterator<T>(this: QueryList<T>): Iterator<T> {
|
||||
* @publicApi
|
||||
*/
|
||||
export class QueryList<T> implements Iterable<T> {
|
||||
public readonly dirty = true;
|
||||
public readonly dirty: boolean = true;
|
||||
private _onDirty?: () => void;
|
||||
private _results: Array<T> = [];
|
||||
private _changesDetected: boolean = false;
|
||||
private _changes: EventEmitter<QueryList<T>>|null = null;
|
||||
@@ -173,9 +174,19 @@ export class QueryList<T> implements Iterable<T> {
|
||||
this._changes.emit(this);
|
||||
}
|
||||
|
||||
/** internal */
|
||||
onDirty(cb: () => void) {
|
||||
this._onDirty = cb;
|
||||
}
|
||||
|
||||
/** internal */
|
||||
setDirty() {
|
||||
(this as {dirty: boolean}).dirty = true;
|
||||
if (this.dirty === false) {
|
||||
(this as {dirty: boolean}).dirty = true;
|
||||
if (this._onDirty) {
|
||||
this._onDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** internal */
|
||||
|
||||
@@ -345,7 +345,7 @@ export interface Directive {
|
||||
/**
|
||||
* // TODO(signals): Remove internal and add public documentation
|
||||
*
|
||||
* @internal
|
||||
* //TODO-internal
|
||||
*/
|
||||
signals?: boolean;
|
||||
|
||||
@@ -640,7 +640,7 @@ export interface Component extends Directive {
|
||||
|
||||
/**
|
||||
* // TODO(signals): Remove internal and add public documentation.
|
||||
* @internal
|
||||
* TODO- internal
|
||||
*/
|
||||
signals?: boolean;
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {assertNotInReactiveContext} from '../core_reactivity_export_internal';
|
||||
import {assertInInjectionContext, Injector, ɵɵdefineInjectable} from '../di';
|
||||
import {inject} from '../di/injector_compatibility';
|
||||
import {ErrorHandler} from '../error_handler';
|
||||
import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
import {DestroyRef} from '../linker/destroy_ref';
|
||||
import {assertNotInReactiveContext} from '../render3/reactivity/asserts';
|
||||
import {assertGreaterThan} from '../util/assert';
|
||||
import {NgZone} from '../zone';
|
||||
|
||||
|
||||
@@ -220,8 +220,8 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
|
||||
hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation, rootViewInjector) :
|
||||
createElementNode(hostRenderer, elementName, getNamespace(elementName));
|
||||
|
||||
// Signal components use the granular "RefreshView" for change detection
|
||||
const signalFlags = (LViewFlags.SignalView | LViewFlags.IsRoot);
|
||||
// Signal components use the granular "RefreshView" for change detection
|
||||
const signalFlags = (LViewFlags.SignalView | LViewFlags.RefreshView | LViewFlags.IsRoot);
|
||||
// Non-signal components use the traditional "CheckAlways or OnPush/Dirty" change detection
|
||||
const nonSignalFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
|
||||
LViewFlags.CheckAlways | LViewFlags.IsRoot;
|
||||
@@ -336,7 +336,7 @@ export class ComponentRef<T> extends AbstractComponentRef<T> {
|
||||
}
|
||||
|
||||
const lView = this._rootLView;
|
||||
setInputsForProperty(lView[TVIEW], lView, dataValue, name, value);
|
||||
setInputsForProperty(lView[TVIEW], lView, dataValue, name, value, /* TODO(signals) */ false);
|
||||
this.previousInputValues.set(name, value);
|
||||
const childComponentLView = getComponentLViewByIndex(this._tNode.index, lView);
|
||||
markViewDirty(childComponentLView);
|
||||
@@ -409,7 +409,7 @@ function createRootComponentView(
|
||||
const viewRenderer = environment.rendererFactory.createRenderer(hostRNode, rootComponentDef);
|
||||
let lViewFlags = LViewFlags.CheckAlways;
|
||||
if (rootComponentDef.signals) {
|
||||
lViewFlags = LViewFlags.SignalView;
|
||||
lViewFlags = LViewFlags.SignalView | LViewFlags.RefreshView;
|
||||
} else if (rootComponentDef.onPush) {
|
||||
lViewFlags = LViewFlags.Dirty;
|
||||
}
|
||||
@@ -445,7 +445,7 @@ function applyRootComponentStyling(
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a root component and sets it up with features and host bindings.Shared by
|
||||
* Creates a root component and sets it up with features and host bindings. Shared by
|
||||
* renderComponent() and ViewContainerRef.createComponent().
|
||||
*/
|
||||
function createRootComponent<T>(
|
||||
|
||||
@@ -297,7 +297,7 @@ export function applyUpdateOpCodes(
|
||||
} else {
|
||||
elementPropertyInternal(
|
||||
tView, tNodeOrTagName, lView, propName, value, lView[RENDERER], sanitizeFn,
|
||||
false);
|
||||
false, /* TODO(signals) */ false);
|
||||
}
|
||||
break;
|
||||
case I18nUpdateOpCode.Text:
|
||||
@@ -348,9 +348,11 @@ function applyIcuUpdateCase(tView: TView, tIcu: TIcu, bindingsStartIndex: number
|
||||
let mask = changeMask;
|
||||
if (activeCaseIndex < 0) {
|
||||
// Clear the flag.
|
||||
// Negative number means that the ICU was freshly created and we need to force the update.
|
||||
// Negative number means that the ICU was freshly created and we need to
|
||||
// force the update.
|
||||
activeCaseIndex = lView[tIcu.currentCaseLViewIndex] = ~activeCaseIndex;
|
||||
// -1 is same as all bits on, which simulates creation since it marks all bits dirty
|
||||
// -1 is same as all bits on, which simulates creation since it marks all
|
||||
// bits dirty
|
||||
mask = -1;
|
||||
}
|
||||
applyUpdateOpCodes(tView, lView, tIcu.update[activeCaseIndex], bindingsStartIndex, mask);
|
||||
|
||||
@@ -80,7 +80,10 @@ export {
|
||||
|
||||
ɵɵprojection,
|
||||
ɵɵprojectionDef,
|
||||
|
||||
ɵɵproperty,
|
||||
ɵɵpropertyCreate,
|
||||
|
||||
ɵɵpropertyInterpolate,
|
||||
ɵɵpropertyInterpolate1,
|
||||
ɵɵpropertyInterpolate2,
|
||||
@@ -91,8 +94,10 @@ export {
|
||||
ɵɵpropertyInterpolate7,
|
||||
ɵɵpropertyInterpolate8,
|
||||
ɵɵpropertyInterpolateV,
|
||||
ɵɵstringifyInterpolation,
|
||||
|
||||
ɵɵreference,
|
||||
ɵɵshallowReference,
|
||||
|
||||
ɵɵrepeater,
|
||||
ɵɵrepeaterCreate,
|
||||
@@ -203,7 +208,9 @@ export {
|
||||
ɵɵcontentQuery,
|
||||
ɵɵloadQuery,
|
||||
ɵɵqueryRefresh,
|
||||
ɵɵviewQuery} from './query';
|
||||
ɵɵviewQuery
|
||||
} from './query';
|
||||
export {ɵɵcontentQueryCreate, ɵɵviewQueryCreate} from './reactivity/queries';
|
||||
export {
|
||||
ɵɵdisableBindings,
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {assertGreaterThan} from '../../util/assert';
|
||||
import {assertIndexInDeclRange} from '../assert';
|
||||
import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks';
|
||||
import {FLAGS, InitPhaseState, LView, LViewFlags, TVIEW, TView} from '../interfaces/view';
|
||||
import {getLView, getSelectedIndex, getTView, isInCheckNoChangesMode, setSelectedIndex} from '../state';
|
||||
import {getLView, getSelectedIndex, getTView, getVirtualInstructionIndex, incrementVirtualInstructionIndex, isInCheckNoChangesMode, setSelectedIndex} from '../state';
|
||||
|
||||
|
||||
/**
|
||||
@@ -45,6 +45,16 @@ export function selectIndexInternal(
|
||||
tView: TView, lView: LView, index: number, checkNoChangesMode: boolean) {
|
||||
ngDevMode && assertIndexInDeclRange(lView[TVIEW], index);
|
||||
|
||||
// Flush virtual instructions up to this point.
|
||||
const {virtualUpdate} = tView;
|
||||
if (virtualUpdate) {
|
||||
for (let idx = getVirtualInstructionIndex();
|
||||
idx < virtualUpdate.length && virtualUpdate[idx].slot < index;
|
||||
idx = incrementVirtualInstructionIndex()) {
|
||||
virtualUpdate[idx].instruction();
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the initial hooks for elements in the view that have been added up to this point.
|
||||
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
||||
if (!checkNoChangesMode) {
|
||||
|
||||
@@ -46,6 +46,7 @@ export * from './namespace';
|
||||
export * from './next_context';
|
||||
export * from './projection';
|
||||
export * from './property';
|
||||
export * from './property_create';
|
||||
export * from './property_interpolation';
|
||||
export * from './storage';
|
||||
export * from './style_map_interpolation';
|
||||
|
||||
@@ -13,11 +13,11 @@ import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} fr
|
||||
import {CONTAINER_HEADER_OFFSET, HAS_CHILD_VIEWS_TO_REFRESH, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS} from '../interfaces/container';
|
||||
import {ComponentTemplate, RenderFlags} from '../interfaces/definition';
|
||||
import {CONTEXT, ENVIRONMENT, FLAGS, InitPhaseState, LView, LViewFlags, PARENT, TVIEW, TView} from '../interfaces/view';
|
||||
import {enterView, isInCheckNoChangesMode, leaveView, setBindingIndex, setIsInCheckNoChangesMode} from '../state';
|
||||
import {enterView, getVirtualInstructionIndex, isInCheckNoChangesMode, leaveView, setBindingIndex, setIsInCheckNoChangesMode} from '../state';
|
||||
import {getFirstLContainer, getNextLContainer} from '../util/view_traversal_utils';
|
||||
import {getComponentLViewByIndex, isCreationMode, markAncestorsForTraversal, markViewForRefresh, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
|
||||
import {executeTemplate, executeViewQueryFn, handleError, processHostBindingOpCodes, refreshContentQueries} from './shared';
|
||||
import {executeTemplate, executeViewQueryFn, flushVirtualInstructionsBefore, handleError, processHostBindingOpCodes, refreshContentQueries} from './shared';
|
||||
|
||||
export function detectChangesInternal<T>(
|
||||
tView: TView, lView: LView, context: T, notifyErrorHandler = true) {
|
||||
|
||||
@@ -34,7 +34,9 @@ export function ɵɵhostProperty<T>(
|
||||
if (bindingUpdated(lView, bindingIndex, value)) {
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, true);
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, true,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
|
||||
}
|
||||
return ɵɵhostProperty;
|
||||
@@ -72,7 +74,8 @@ export function ɵɵsyntheticHostProperty<T>(
|
||||
const tNode = getSelectedTNode();
|
||||
const currentDef = getCurrentDirectiveDef(tView.data);
|
||||
const renderer = loadComponentRenderer(currentDef, tNode, lView);
|
||||
elementPropertyInternal(tView, tNode, lView, propName, value, renderer, sanitizer, true);
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, value, renderer, sanitizer, true, /* TODO(signals) */ false);
|
||||
ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
|
||||
}
|
||||
return ɵɵsyntheticHostProperty;
|
||||
|
||||
@@ -33,14 +33,16 @@ import {elementPropertyInternal, setInputsForProperty, storePropertyBindingMetad
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵproperty<T>(
|
||||
propName: string, value: T, sanitizer?: SanitizerFn|null): typeof ɵɵproperty {
|
||||
propName: string, value: T, sanitizer?: SanitizerFn|null,
|
||||
opts?: {skipSignal: boolean}): typeof ɵɵproperty {
|
||||
const lView = getLView();
|
||||
const bindingIndex = nextBindingIndex();
|
||||
if (bindingUpdated(lView, bindingIndex, value)) {
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false,
|
||||
!!opts?.skipSignal);
|
||||
ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
|
||||
}
|
||||
return ɵɵproperty;
|
||||
@@ -55,5 +57,5 @@ export function setDirectiveInputsWhichShadowsStyling(
|
||||
const inputs = tNode.inputs!;
|
||||
const property = isClassBased ? 'class' : 'style';
|
||||
// We support both 'class' and `className` hence the fallback.
|
||||
setInputsForProperty(tView, lView, inputs[property], property, value);
|
||||
setInputsForProperty(tView, lView, inputs[property], property, value, false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {SIGNAL} from '@angular/core/primitives/signals';
|
||||
|
||||
import {validateAgainstEventProperties} from '../../sanitization/sanitization';
|
||||
import {assertDefined, assertIndexInRange} from '../../util/assert';
|
||||
import {EMPTY_ARRAY} from '../../util/empty';
|
||||
import {bindingUpdated} from '../bindings';
|
||||
import {DirectiveDef} from '../interfaces/definition';
|
||||
import {PropertyAliasValue, TNode} from '../interfaces/node';
|
||||
import {RComment, RElement} from '../interfaces/renderer_dom';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponentHost} from '../interfaces/type_checks';
|
||||
import {HEADER_OFFSET, RENDERER} from '../interfaces/view';
|
||||
import {computed} from '../reactivity/computed';
|
||||
import {InputSignalNode} from '../reactivity/input_signal';
|
||||
import {getCurrentTNode, getLView, getTView, nextBindingIndex} from '../state';
|
||||
import {renderStringify} from '../util/stringify_utils';
|
||||
import {getNativeByTNode} from '../util/view_utils';
|
||||
|
||||
import {handleUnknownPropertyError, isPropertyValid} from './element_validation';
|
||||
import {mapPropName, markDirtyIfOnPush, setNgReflectProperties, writeToDirectiveInput} from './shared';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpropertyCreate<T>(
|
||||
slot: number, propName: string, expr: () => T,
|
||||
sanitizer?: SanitizerFn|null): typeof ɵɵpropertyCreate {
|
||||
const lView = getLView();
|
||||
const expressionSlot = HEADER_OFFSET + slot;
|
||||
|
||||
const tView = getTView();
|
||||
const tNode = getCurrentTNode();
|
||||
assertDefined(tNode, `propertyCreate() must follow an actual element`);
|
||||
|
||||
const inputData = tNode.inputs?.[propName] ?? EMPTY_ARRAY;
|
||||
|
||||
let signalInputs: InputSignalNode<unknown, unknown>[]|null = null;
|
||||
|
||||
// PERF: the fact that we need to iterate over all the inputs here isn't great.
|
||||
// We might consider storing more info on TView
|
||||
let zoneTargets: PropertyAliasValue|null = null;
|
||||
for (let i = 0; i < inputData.length;) {
|
||||
const directiveIndex = inputData[i++] as number;
|
||||
const privateName = inputData[i++] as string;
|
||||
const def = tView.data[directiveIndex] as DirectiveDef<any>;
|
||||
if (!def.signals) {
|
||||
// TODO(pk): refactor - code flow with all those firstCreatePass checks becomes hard to follow
|
||||
if (tView.firstCreatePass) {
|
||||
(zoneTargets ??= []).push(directiveIndex, privateName);
|
||||
}
|
||||
} else {
|
||||
ngDevMode && assertIndexInRange(lView, directiveIndex);
|
||||
// PERF: megamorphic read on [privateName] access
|
||||
const inputSignal =
|
||||
lView[directiveIndex][privateName][SIGNAL] as InputSignalNode<unknown, unknown>;
|
||||
(signalInputs ??= []).push(inputSignal);
|
||||
}
|
||||
}
|
||||
|
||||
zoneTargets ??= EMPTY_ARRAY;
|
||||
signalInputs ??= EMPTY_ARRAY;
|
||||
|
||||
// PERF(pk): I could avoid wrapping into computed for the case of a single binding to a signal
|
||||
// based component
|
||||
expr = computed(expr);
|
||||
|
||||
lView[expressionSlot] = expr;
|
||||
for (const inputSignal of signalInputs) {
|
||||
// TODO: Improve this by not allocating an object literal here. This exists just for testing.
|
||||
inputSignal.bind(inputSignal, {computation: expr});
|
||||
|
||||
// TODO: figure out where to set `isInitialized`.
|
||||
inputSignal.isInitialized = true;
|
||||
}
|
||||
|
||||
if (tView.firstCreatePass) {
|
||||
if (inputData.length === 0) {
|
||||
// Untargeted input -> DOM binding.
|
||||
(tView.virtualUpdate ??= []).push({
|
||||
slot: expressionSlot,
|
||||
instruction: () =>
|
||||
propertyUpdateDom(tNode.index, propName, expressionSlot, sanitizer ?? null),
|
||||
});
|
||||
} else if (zoneTargets.length) {
|
||||
// Some binding targets were zone-based, so we need an update instruction to process them.
|
||||
(tView.virtualUpdate ??= []).push({
|
||||
slot: expressionSlot,
|
||||
instruction: () => propertyUpdateInput(tNode.index, propName, expressionSlot, zoneTargets!),
|
||||
});
|
||||
} else {
|
||||
// The only target(s) were signal-based, so no update path is needed.
|
||||
}
|
||||
}
|
||||
|
||||
return ɵɵpropertyCreate;
|
||||
}
|
||||
|
||||
export function propertyUpdateDom(
|
||||
nodeSlot: number, propName: string, expressionSlot: number, sanitizer: SanitizerFn|null): void {
|
||||
const lView = getLView();
|
||||
const expr = lView[expressionSlot];
|
||||
|
||||
let value = expr();
|
||||
const bindingIndex = nextBindingIndex();
|
||||
if (!bindingUpdated(lView, bindingIndex, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tView = getTView();
|
||||
const tNode = tView.data[nodeSlot] as TNode;
|
||||
const element = getNativeByTNode(tNode, lView) as RElement | RComment;
|
||||
|
||||
propName = mapPropName(propName);
|
||||
|
||||
if (ngDevMode) {
|
||||
validateAgainstEventProperties(propName);
|
||||
if (!isPropertyValid(element, propName, tNode.value, tView.schemas)) {
|
||||
handleUnknownPropertyError(propName, tNode.value, tNode.type, lView);
|
||||
}
|
||||
ngDevMode.rendererSetProperty++;
|
||||
}
|
||||
|
||||
// It is assumed that the sanitizer is only added when the compiler determines that the
|
||||
// property is risky, so sanitization can be done without further checks.
|
||||
value = sanitizer != null ? (sanitizer(value, tNode.value || '', propName) as any) : value;
|
||||
lView[RENDERER].setProperty(element as RElement, propName, value);
|
||||
}
|
||||
|
||||
export function propertyUpdateInput(
|
||||
nodeIndex: number, propName: string, expressionSlot: number,
|
||||
targets: PropertyAliasValue): void {
|
||||
const lView = getLView();
|
||||
const expr = lView[expressionSlot];
|
||||
const value = expr();
|
||||
|
||||
const tView = getTView();
|
||||
const tNode = tView.data[nodeIndex] as TNode;
|
||||
|
||||
ngDevMode && assertDefined(tNode.inputs, `Expected tNode to have inputs`);
|
||||
|
||||
const bindingIndex = nextBindingIndex();
|
||||
if (!bindingUpdated(lView, bindingIndex, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < targets.length;) {
|
||||
const index = targets[i++] as number;
|
||||
const privateName = targets[i++] as string;
|
||||
ngDevMode && assertIndexInRange(lView, index);
|
||||
const instance = lView[index];
|
||||
const def = tView.data[index] as DirectiveDef<any>;
|
||||
|
||||
writeToDirectiveInput(def, instance, propName, privateName, value);
|
||||
}
|
||||
|
||||
const element = getNativeByTNode(tNode, lView) as RElement | RComment;
|
||||
if (isComponentHost(tNode)) {
|
||||
markDirtyIfOnPush(lView, tNode.index);
|
||||
}
|
||||
if (ngDevMode) {
|
||||
setNgReflectProperties(lView, element, tNode.type, targets, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function ɵɵstringifyInterpolation(
|
||||
staticStrings: TemplateStringsArray, ...expressionValues: any[]): string {
|
||||
// Build the updated content
|
||||
let content = staticStrings[0];
|
||||
for (let i = 1; i < staticStrings.length; i++) {
|
||||
content += renderStringify(expressionValues[i - 1]) + staticStrings[i];
|
||||
}
|
||||
return content;
|
||||
}
|
||||
@@ -87,7 +87,8 @@ export function ɵɵpropertyInterpolate1(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 1, prefix, suffix);
|
||||
@@ -134,7 +135,8 @@ export function ɵɵpropertyInterpolate2(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 2, prefix, i0, suffix);
|
||||
@@ -184,7 +186,8 @@ export function ɵɵpropertyInterpolate3(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 3, prefix, i0, i1, suffix);
|
||||
@@ -236,7 +239,8 @@ export function ɵɵpropertyInterpolate4(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 4, prefix, i0, i1, i2, suffix);
|
||||
@@ -292,7 +296,8 @@ export function ɵɵpropertyInterpolate5(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 5, prefix, i0, i1, i2, i3, suffix);
|
||||
@@ -350,7 +355,8 @@ export function ɵɵpropertyInterpolate6(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 6, prefix, i0, i1, i2, i3, i4, suffix);
|
||||
@@ -410,7 +416,8 @@ export function ɵɵpropertyInterpolate7(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 7, prefix, i0, i1, i2, i3, i4, i5,
|
||||
@@ -473,7 +480,8 @@ export function ɵɵpropertyInterpolate8(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
ngDevMode &&
|
||||
storePropertyBindingMetadata(
|
||||
tView.data, tNode, propName, getBindingIndex() - 8, prefix, i0, i1, i2, i3, i4, i5, i6,
|
||||
@@ -520,7 +528,8 @@ export function ɵɵpropertyInterpolateV(
|
||||
const tView = getTView();
|
||||
const tNode = getSelectedTNode();
|
||||
elementPropertyInternal(
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
|
||||
tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false,
|
||||
/* TODO(signals) */ false);
|
||||
if (ngDevMode) {
|
||||
const interpolationInBetween = [values[0]]; // prefix
|
||||
for (let i = 2; i < values.length; i += 2) {
|
||||
|
||||
@@ -44,7 +44,7 @@ import {clearElementContents, updateTextNode} from '../node_manipulation';
|
||||
import {isInlineTemplate, isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
import {profiler, ProfilerEvent} from '../profiler';
|
||||
import {commitLViewConsumerIfHasProducers, getReactiveLViewConsumer} from '../reactive_lview_consumer';
|
||||
import {getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, isInSkipHydrationBlock, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setSelectedIndex} from '../state';
|
||||
import {getBindingsEnabled, getCurrentDirectiveIndex, getCurrentParentTNode, getCurrentTNodePlaceholderOk, getSelectedIndex, getVirtualInstructionIndex, isCurrentTNodeParent, isInCheckNoChangesMode, isInI18nBlock, isInSkipHydrationBlock, setBindingRootForHostBindings, setCurrentDirectiveIndex, setCurrentQueryIndex, setCurrentTNode, setSelectedIndex} from '../state';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {mergeHostAttrs} from '../util/attrs_utils';
|
||||
import {INTERPOLATION_DELIMITER} from '../util/misc_utils';
|
||||
@@ -277,6 +277,13 @@ export function executeTemplate<T>(
|
||||
effectiveConsumer.dirty = false;
|
||||
}
|
||||
templateFn(rf, context);
|
||||
|
||||
// Flush remaining virtual instructions.
|
||||
if (isUpdatePhase && tView.virtualUpdate) {
|
||||
for (let idx = getVirtualInstructionIndex(); idx < tView.virtualUpdate.length; idx++) {
|
||||
tView.virtualUpdate[idx].instruction();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
consumerAfterComputation(effectiveConsumer, prevConsumer);
|
||||
}
|
||||
@@ -398,7 +405,7 @@ export function createTView(
|
||||
const initialViewLength = bindingStartIndex + vars;
|
||||
const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
|
||||
const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory;
|
||||
const tView = blueprint[TVIEW as any] = {
|
||||
const tView: TView = blueprint[TVIEW as any] = {
|
||||
type: type,
|
||||
blueprint: blueprint,
|
||||
template: templateFn,
|
||||
@@ -429,6 +436,7 @@ export function createTView(
|
||||
schemas: schemas,
|
||||
consts: consts,
|
||||
incompleteFirstPass: false,
|
||||
virtualUpdate: null,
|
||||
ssrId,
|
||||
};
|
||||
if (ngDevMode) {
|
||||
@@ -748,7 +756,7 @@ function initializeInputAndOutputAliases(
|
||||
* Note: this mapping has to be kept in sync with the equally named mapping in the template
|
||||
* type-checking machinery of ngtsc.
|
||||
*/
|
||||
function mapPropName(name: string): string {
|
||||
export function mapPropName(name: string): string {
|
||||
if (name === 'class') return 'className';
|
||||
if (name === 'for') return 'htmlFor';
|
||||
if (name === 'formaction') return 'formAction';
|
||||
@@ -760,13 +768,16 @@ function mapPropName(name: string): string {
|
||||
|
||||
export function elementPropertyInternal<T>(
|
||||
tView: TView, tNode: TNode, lView: LView, propName: string, value: T, renderer: Renderer,
|
||||
sanitizer: SanitizerFn|null|undefined, nativeOnly: boolean): void {
|
||||
sanitizer: SanitizerFn|null|undefined, nativeOnly: boolean, skipSignal: boolean): void {
|
||||
ngDevMode && assertNotSame(value, NO_CHANGE as any, 'Incoming value should never be NO_CHANGE.');
|
||||
const element = getNativeByTNode(tNode, lView) as RElement | RComment;
|
||||
let inputData = tNode.inputs;
|
||||
let dataValue: PropertyAliasValue|undefined;
|
||||
|
||||
|
||||
// TODO(signals): use prop target here
|
||||
if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
|
||||
setInputsForProperty(tView, lView, dataValue, propName, value);
|
||||
setInputsForProperty(tView, lView, dataValue, propName, value, skipSignal);
|
||||
if (isComponentHost(tNode)) markDirtyIfOnPush(lView, tNode.index);
|
||||
if (ngDevMode) {
|
||||
setNgReflectProperties(lView, element, tNode.type, dataValue, value);
|
||||
@@ -1241,7 +1252,7 @@ function addComponentLogic<T>(lView: LView, hostTNode: TElementNode, def: Compon
|
||||
const rendererFactory = lView[ENVIRONMENT].rendererFactory;
|
||||
let lViewFlags = LViewFlags.CheckAlways;
|
||||
if (def.signals) {
|
||||
lViewFlags = LViewFlags.SignalView;
|
||||
lViewFlags = LViewFlags.SignalView | LViewFlags.RefreshView;
|
||||
} else if (def.onPush) {
|
||||
lViewFlags = LViewFlags.Dirty;
|
||||
}
|
||||
@@ -1316,7 +1327,7 @@ function setInputsFromAttrs<T>(
|
||||
}
|
||||
}
|
||||
|
||||
function writeToDirectiveInput<T>(
|
||||
export function writeToDirectiveInput<T>(
|
||||
def: DirectiveDef<T>, instance: T, publicName: string, privateName: string, value: string) {
|
||||
const prevConsumer = setActiveConsumer(null);
|
||||
try {
|
||||
@@ -1577,7 +1588,8 @@ export function handleError(lView: LView, error: any): void {
|
||||
* @param value Value to set.
|
||||
*/
|
||||
export function setInputsForProperty(
|
||||
tView: TView, lView: LView, inputs: PropertyAliasValue, publicName: string, value: any): void {
|
||||
tView: TView, lView: LView, inputs: PropertyAliasValue, publicName: string, value: any,
|
||||
skipSignal: boolean): void {
|
||||
for (let i = 0; i < inputs.length;) {
|
||||
const index = inputs[i++] as number;
|
||||
const privateName = inputs[i++] as string;
|
||||
@@ -1585,6 +1597,11 @@ export function setInputsForProperty(
|
||||
ngDevMode && assertIndexInRange(lView, index);
|
||||
const def = tView.data[index] as DirectiveDef<any>;
|
||||
|
||||
// TODO(signals): not ideal. just for prototyping.
|
||||
if (def.signals && skipSignal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
writeToDirectiveInput(def, instance, publicName, privateName, value);
|
||||
}
|
||||
}
|
||||
@@ -1600,3 +1617,5 @@ export function textBindingInternal(lView: LView, index: number, value: string):
|
||||
ngDevMode && assertDefined(element, 'native element should exist');
|
||||
updateTextNode(lView[RENDERER], element, value);
|
||||
}
|
||||
|
||||
export function flushVirtualInstructionsBefore() {}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {PropertyAliases, PropertyAliasValue, TNode, TNodeType} from '../interfaces/node';
|
||||
|
||||
export enum TargetType {
|
||||
Input,
|
||||
DomBinding,
|
||||
Container,
|
||||
}
|
||||
|
||||
export interface InputTarget {
|
||||
kind: TargetType.Input;
|
||||
data: PropertyAliasValue;
|
||||
}
|
||||
|
||||
export interface DomBindingTarget {
|
||||
kind: TargetType.DomBinding;
|
||||
}
|
||||
|
||||
export interface ContainerTarget {
|
||||
kind: TargetType.Container;
|
||||
}
|
||||
|
||||
export function analyzePropertyForElement(tNode: TNode, propName: string): InputTarget|
|
||||
DomBindingTarget|ContainerTarget|null {
|
||||
const inputs = tNode.inputs;
|
||||
let inputData: PropertyAliasValue|null = null;
|
||||
if (inputs !== null && (inputData = inputs[propName]) !== undefined) {
|
||||
return {kind: TargetType.Input, data: inputData};
|
||||
}
|
||||
if (tNode.type & TNodeType.AnyRNode) {
|
||||
return {kind: TargetType.DomBinding};
|
||||
}
|
||||
if (tNode.type & TNodeType.AnyContainer) {
|
||||
return {kind: TargetType.DomBinding};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {HEADER_OFFSET, LView, TView} from '../interfaces/view';
|
||||
import {getContextLView} from '../state';
|
||||
import {getContextLView, getLView} from '../state';
|
||||
import {load} from '../util/view_utils';
|
||||
|
||||
|
||||
@@ -35,3 +35,14 @@ export function ɵɵreference<T>(index: number) {
|
||||
const contextLView = getContextLView();
|
||||
return load<T>(contextLView, HEADER_OFFSET + index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a local reference from the current `LView`.
|
||||
*
|
||||
* @param index The relative index of the local ref in `LView`.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵshallowReference<T>(index: number): T {
|
||||
return load<T>(getLView(), HEADER_OFFSET + index);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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
|
||||
*/
|
||||
@@ -11,6 +11,7 @@ import {ProviderToken} from '../../di/provider_token';
|
||||
import {DehydratedView} from '../../hydration/interfaces';
|
||||
import {SchemaMetadata} from '../../metadata/schema';
|
||||
import {Sanitizer} from '../../sanitization/sanitizer';
|
||||
|
||||
import type {ReactiveLViewConsumer} from '../reactive_lview_consumer';
|
||||
import type {FlushableEffectRunner} from '../reactivity/effect';
|
||||
import type {AfterRenderEventManager} from '../after_render_hooks';
|
||||
@@ -841,6 +842,8 @@ export interface TView {
|
||||
* (see `getComponentId` function for details)
|
||||
*/
|
||||
ssrId: string|null;
|
||||
|
||||
virtualUpdate: TVirtualInstruction[]|null;
|
||||
}
|
||||
|
||||
/** Single hook callback function. */
|
||||
@@ -917,6 +920,11 @@ export type TData =
|
||||
(TNode|PipeDef<any>|DirectiveDef<any>|ComponentDef<any>|number|TStylingRange|TStylingKey|
|
||||
ProviderToken<any>|TI18n|I18nUpdateOpCodes|TIcu|null|string|TDeferBlockDetails)[];
|
||||
|
||||
export interface TVirtualInstruction {
|
||||
slot: number;
|
||||
instruction: () => void;
|
||||
}
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
||||
@@ -89,6 +89,8 @@ export const angularCoreEnv: {[name: string]: Function} =
|
||||
'ɵɵprojectionDef': r3.ɵɵprojectionDef,
|
||||
'ɵɵhostProperty': r3.ɵɵhostProperty,
|
||||
'ɵɵproperty': r3.ɵɵproperty,
|
||||
'ɵɵpropertyCreate': r3.ɵɵpropertyCreate,
|
||||
'ɵɵstringifyInterpolation': r3.ɵɵstringifyInterpolation,
|
||||
'ɵɵpropertyInterpolate': r3.ɵɵpropertyInterpolate,
|
||||
'ɵɵpropertyInterpolate1': r3.ɵɵpropertyInterpolate1,
|
||||
'ɵɵpropertyInterpolate2': r3.ɵɵpropertyInterpolate2,
|
||||
@@ -100,11 +102,14 @@ export const angularCoreEnv: {[name: string]: Function} =
|
||||
'ɵɵpropertyInterpolate8': r3.ɵɵpropertyInterpolate8,
|
||||
'ɵɵpropertyInterpolateV': r3.ɵɵpropertyInterpolateV,
|
||||
'ɵɵpipe': r3.ɵɵpipe,
|
||||
'ɵɵviewQueryCreate': r3.ɵɵviewQueryCreate,
|
||||
'ɵɵcontentQueryCreate': r3.ɵɵcontentQueryCreate,
|
||||
'ɵɵqueryRefresh': r3.ɵɵqueryRefresh,
|
||||
'ɵɵviewQuery': r3.ɵɵviewQuery,
|
||||
'ɵɵloadQuery': r3.ɵɵloadQuery,
|
||||
'ɵɵcontentQuery': r3.ɵɵcontentQuery,
|
||||
'ɵɵreference': r3.ɵɵreference,
|
||||
'ɵɵshallowReference': r3.ɵɵshallowReference,
|
||||
'ɵɵclassMap': r3.ɵɵclassMap,
|
||||
'ɵɵclassMapInterpolate1': r3.ɵɵclassMapInterpolate1,
|
||||
'ɵɵclassMapInterpolate2': r3.ɵɵclassMapInterpolate2,
|
||||
|
||||
@@ -7,11 +7,19 @@
|
||||
*/
|
||||
|
||||
import {assertIndexInRange} from '../util/assert';
|
||||
|
||||
import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4, getBinding, updateBinding} from './bindings';
|
||||
import {LView} from './interfaces/view';
|
||||
import {getBindingRoot, getLView} from './state';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
|
||||
let pureFunctionsEnabled = true;
|
||||
|
||||
export function setPureFunctionsEnabled(value: boolean): boolean {
|
||||
const prev = pureFunctionsEnabled;
|
||||
pureFunctionsEnabled = value;
|
||||
return prev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bindings for pure functions are stored after regular bindings.
|
||||
@@ -43,6 +51,8 @@ import {NO_CHANGE} from './tokens';
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵpureFunction0<T>(slotOffset: number, pureFn: () => T, thisArg?: any): T {
|
||||
if (!pureFunctionsEnabled) return thisArg ? pureFn.call(thisArg) : pureFn();
|
||||
|
||||
const bindingIndex = getBindingRoot() + slotOffset;
|
||||
const lView = getLView();
|
||||
return lView[bindingIndex] === NO_CHANGE ?
|
||||
@@ -308,9 +318,11 @@ export function pureFunction1Internal(
|
||||
lView: LView, bindingRoot: number, slotOffset: number, pureFn: (v: any) => any, exp: any,
|
||||
thisArg?: any): any {
|
||||
const bindingIndex = bindingRoot + slotOffset;
|
||||
return bindingUpdated(lView, bindingIndex, exp) ?
|
||||
updateBinding(lView, bindingIndex + 1, thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) :
|
||||
getPureFunctionReturnValue(lView, bindingIndex + 1);
|
||||
if (pureFunctionsEnabled && !bindingUpdated(lView, bindingIndex, exp)) {
|
||||
return getPureFunctionReturnValue(lView, bindingIndex + 1);
|
||||
}
|
||||
const value = thisArg ? pureFn.call(thisArg, exp) : pureFn(exp);
|
||||
return pureFunctionsEnabled ? updateBinding(lView, bindingIndex + 1, value) : value;
|
||||
}
|
||||
|
||||
|
||||
@@ -331,11 +343,11 @@ export function pureFunction2Internal(
|
||||
lView: LView, bindingRoot: number, slotOffset: number, pureFn: (v1: any, v2: any) => any,
|
||||
exp1: any, exp2: any, thisArg?: any): any {
|
||||
const bindingIndex = bindingRoot + slotOffset;
|
||||
return bindingUpdated2(lView, bindingIndex, exp1, exp2) ?
|
||||
updateBinding(
|
||||
lView, bindingIndex + 2,
|
||||
thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) :
|
||||
getPureFunctionReturnValue(lView, bindingIndex + 2);
|
||||
if (pureFunctionsEnabled && !bindingUpdated2(lView, bindingIndex, exp1, exp2)) {
|
||||
return getPureFunctionReturnValue(lView, bindingIndex + 2);
|
||||
}
|
||||
const value = thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2);
|
||||
return pureFunctionsEnabled ? updateBinding(lView, bindingIndex + 2, value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,11 +369,11 @@ export function pureFunction3Internal(
|
||||
pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any,
|
||||
thisArg?: any): any {
|
||||
const bindingIndex = bindingRoot + slotOffset;
|
||||
return bindingUpdated3(lView, bindingIndex, exp1, exp2, exp3) ?
|
||||
updateBinding(
|
||||
lView, bindingIndex + 3,
|
||||
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) :
|
||||
getPureFunctionReturnValue(lView, bindingIndex + 3);
|
||||
if (pureFunctionsEnabled && !bindingUpdated3(lView, bindingIndex, exp1, exp2, exp3)) {
|
||||
return getPureFunctionReturnValue(lView, bindingIndex + 3);
|
||||
}
|
||||
const value = thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3);
|
||||
return pureFunctionsEnabled ? updateBinding(lView, bindingIndex + 3, value) : value;
|
||||
}
|
||||
|
||||
|
||||
@@ -386,11 +398,12 @@ export function pureFunction4Internal(
|
||||
pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, exp4: any,
|
||||
thisArg?: any): any {
|
||||
const bindingIndex = bindingRoot + slotOffset;
|
||||
return bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4) ?
|
||||
updateBinding(
|
||||
lView, bindingIndex + 4,
|
||||
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) :
|
||||
getPureFunctionReturnValue(lView, bindingIndex + 4);
|
||||
if (pureFunctionsEnabled && !bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4)) {
|
||||
return getPureFunctionReturnValue(lView, bindingIndex + 4);
|
||||
}
|
||||
const value =
|
||||
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4);
|
||||
return pureFunctionsEnabled ? updateBinding(lView, bindingIndex + 4, value) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,9 +426,15 @@ export function pureFunctionVInternal(
|
||||
exps: any[], thisArg?: any): any {
|
||||
let bindingIndex = bindingRoot + slotOffset;
|
||||
let different = false;
|
||||
for (let i = 0; i < exps.length; i++) {
|
||||
bindingUpdated(lView, bindingIndex++, exps[i]) && (different = true);
|
||||
if (pureFunctionsEnabled) {
|
||||
for (let i = 0; i < exps.length; i++) {
|
||||
bindingUpdated(lView, bindingIndex++, exps[i]) && (different = true);
|
||||
}
|
||||
if (!different) {
|
||||
return getPureFunctionReturnValue(lView, bindingIndex);
|
||||
}
|
||||
}
|
||||
return different ? updateBinding(lView, bindingIndex, pureFn.apply(thisArg, exps)) :
|
||||
getPureFunctionReturnValue(lView, bindingIndex);
|
||||
|
||||
const value = pureFn.apply(thisArg, exps);
|
||||
return pureFunctionsEnabled ? updateBinding(lView, bindingIndex, value) : value;
|
||||
}
|
||||
|
||||
@@ -418,12 +418,18 @@ function collectQueryResults<T>(tView: TView, lView: LView, queryIndex: number,
|
||||
*/
|
||||
export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
|
||||
const lView = getLView();
|
||||
const tView = getTView();
|
||||
const queryIndex = getCurrentQueryIndex();
|
||||
|
||||
setCurrentQueryIndex(queryIndex + 1);
|
||||
return queryRefreshInternal(lView, queryIndex);
|
||||
}
|
||||
|
||||
export function queryRefreshInternal(lView: LView, queryIndex: number): boolean {
|
||||
const tView = lView[TVIEW];
|
||||
const tQuery = getTQuery(tView, queryIndex);
|
||||
const lQuery = lView[QUERIES]!.queries![queryIndex];
|
||||
const queryList = lQuery.queryList;
|
||||
|
||||
if (queryList.dirty &&
|
||||
(isCreationMode(lView) ===
|
||||
((tQuery.metadata.flags & QueryFlags.isStatic) === QueryFlags.isStatic))) {
|
||||
@@ -453,6 +459,12 @@ export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
|
||||
*/
|
||||
export function ɵɵviewQuery<T>(
|
||||
predicate: ProviderToken<unknown>|string[], flags: QueryFlags, read?: any): void {
|
||||
createViewQueryInternal(getLView(), predicate, flags, read);
|
||||
}
|
||||
|
||||
export function createViewQueryInternal<T>(
|
||||
lView: LView, predicate: ProviderToken<unknown>|string[], flags: QueryFlags,
|
||||
read?: any): number {
|
||||
ngDevMode && assertNumber(flags, 'Expecting flags');
|
||||
const tView = getTView();
|
||||
if (tView.firstCreatePass) {
|
||||
@@ -461,7 +473,7 @@ export function ɵɵviewQuery<T>(
|
||||
tView.staticViewQueries = true;
|
||||
}
|
||||
}
|
||||
createLQuery<T>(tView, getLView(), flags);
|
||||
return createLQuery<T>(tView, lView, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -479,6 +491,16 @@ export function ɵɵviewQuery<T>(
|
||||
export function ɵɵcontentQuery<T>(
|
||||
directiveIndex: number, predicate: ProviderToken<unknown>|string[], flags: QueryFlags,
|
||||
read?: any): void {
|
||||
createContentQueryInternal<T>(getLView(), directiveIndex, predicate, flags, read);
|
||||
}
|
||||
|
||||
export function createContentQueryInternal<T>(
|
||||
lView: LView,
|
||||
directiveIndex: number,
|
||||
predicate: ProviderToken<unknown>|string[],
|
||||
flags: QueryFlags,
|
||||
read?: any,
|
||||
) {
|
||||
ngDevMode && assertNumber(flags, 'Expecting flags');
|
||||
const tView = getTView();
|
||||
if (tView.firstCreatePass) {
|
||||
@@ -490,7 +512,7 @@ export function ɵɵcontentQuery<T>(
|
||||
}
|
||||
}
|
||||
|
||||
createLQuery<T>(tView, getLView(), flags);
|
||||
return createLQuery<T>(tView, lView, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,20 +524,22 @@ export function ɵɵloadQuery<T>(): QueryList<T> {
|
||||
return loadQueryInternal<T>(getLView(), getCurrentQueryIndex());
|
||||
}
|
||||
|
||||
function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
|
||||
export function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
|
||||
ngDevMode &&
|
||||
assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
|
||||
ngDevMode && assertIndexInRange(lView[QUERIES]!.queries, queryIndex);
|
||||
return lView[QUERIES]!.queries[queryIndex].queryList;
|
||||
}
|
||||
|
||||
function createLQuery<T>(tView: TView, lView: LView, flags: QueryFlags) {
|
||||
function createLQuery<T>(tView: TView, lView: LView, flags: QueryFlags): number {
|
||||
const queryList = new QueryList<T>(
|
||||
(flags & QueryFlags.emitDistinctChangesOnly) === QueryFlags.emitDistinctChangesOnly);
|
||||
storeCleanupWithContext(tView, lView, queryList, queryList.destroy);
|
||||
|
||||
if (lView[QUERIES] === null) lView[QUERIES] = new LQueries_();
|
||||
lView[QUERIES]!.queries.push(new LQuery_(queryList));
|
||||
|
||||
return lView[QUERIES]!.queries.length - 1;
|
||||
}
|
||||
|
||||
function createTQuery(tView: TView, metadata: TQueryMetadata, nodeIndex: number): void {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {ERRORED, producerAccessed, producerUpdateValueVersion, SIGNAL} from '@angular/core/primitives/signals';
|
||||
|
||||
import {INPUT_SIGNAL_NODE, InputSignal, InputSignalNode} from './input_signal';
|
||||
|
||||
export interface PrimaryInputOptions<ReadT, WriteT> {
|
||||
alias?: string;
|
||||
transform?: (value: WriteT) => ReadT;
|
||||
}
|
||||
|
||||
export interface InputOptions<ReadT, WriteT> extends PrimaryInputOptions<ReadT, WriteT> {
|
||||
initialValue?: WriteT;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export function input(): InputSignal<undefined, undefined>;
|
||||
export function input<T>(): InputSignal<T|undefined, T>;
|
||||
export function input<T>(
|
||||
initialValue: T&(string | number | boolean),
|
||||
opts?: PrimaryInputOptions<T, T>&{transform?: undefined}): InputSignal<T, T>;
|
||||
export function input<ReadT, WriteT = ReadT>(
|
||||
initialValue: WriteT&(string | number | boolean),
|
||||
opts: PrimaryInputOptions<ReadT, WriteT>): InputSignal<ReadT, WriteT>;
|
||||
export function input<T>(opts: InputOptions<T, T>&
|
||||
{required: true, transform?: undefined}): InputSignal<T, T>;
|
||||
export function input<ReadT, WriteT = ReadT>(opts: InputOptions<ReadT, WriteT>&
|
||||
{required: true}): InputSignal<ReadT, WriteT>;
|
||||
export function input<T>(opts: InputOptions<T, T>&
|
||||
{initialValue: T, transform?: undefined}): InputSignal<T, T>;
|
||||
export function input<ReadT, WriteT = ReadT>(opts: InputOptions<ReadT, WriteT>&
|
||||
{initialValue: ReadT}): InputSignal<ReadT, WriteT>;
|
||||
export function input<ReadT, WriteT = ReadT>(opts: InputOptions<ReadT, WriteT>):
|
||||
InputSignal<ReadT|undefined, WriteT>;
|
||||
export function input<ReadT, WriteT>(opts?: InputOptions<ReadT, WriteT>):
|
||||
InputSignal<ReadT, WriteT> {
|
||||
const node: InputSignalNode<ReadT, WriteT> = Object.create(INPUT_SIGNAL_NODE);
|
||||
|
||||
opts?.transform && (node.transform = opts.transform);
|
||||
opts?.initialValue && (node.value = node.transform(opts.initialValue));
|
||||
|
||||
function inputValueFn() {
|
||||
// Check if the value needs updating before returning it.
|
||||
producerUpdateValueVersion(node);
|
||||
|
||||
// Record that someone looked at this signal.
|
||||
producerAccessed(node);
|
||||
|
||||
if (node.value === ERRORED) {
|
||||
throw node.error;
|
||||
}
|
||||
|
||||
return node.value;
|
||||
}
|
||||
|
||||
(inputValueFn as any)[SIGNAL] = node;
|
||||
|
||||
return inputValueFn as InputSignal<ReadT, WriteT>;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {COMPUTED_NODE, ComputedNode, UNSET} from '@angular/core/primitives/signals';
|
||||
|
||||
import {setPureFunctionsEnabled} from '../pure_function';
|
||||
|
||||
import {Signal} from './api';
|
||||
|
||||
export const BRAND_WRITE_TYPE = /* @__PURE__ */ Symbol();
|
||||
|
||||
/**
|
||||
* A `Signal` representing a component or directive input.
|
||||
*
|
||||
* This is equivalent to a `Signal`, except it also carries type information about the
|
||||
*/
|
||||
export type InputSignal<ReadT, WriteT> = Signal<ReadT>&{
|
||||
[BRAND_WRITE_TYPE]: WriteT;
|
||||
};
|
||||
|
||||
// TODO(signals)
|
||||
export type ɵɵGetInputSignalWriteType<T> = T extends InputSignal<any, infer X>? X : never;
|
||||
|
||||
export interface InputSignalNode<ReadT, WriteT> extends ComputedNode<ReadT> {
|
||||
/**
|
||||
* Whether the input signal is initialized. If not, accessing the node results in an error.
|
||||
*
|
||||
* We support this to enable required inputs which may be set later, but should not be accessed
|
||||
* before as there is no meaningful value and this usually indicates incorrect user code.
|
||||
*/
|
||||
isInitialized: boolean;
|
||||
|
||||
/**
|
||||
* The computation to which the input is currently bound. An input signal is commonly
|
||||
* bound to a computation if a parent signal component binds to the input.
|
||||
*/
|
||||
_boundComputation?: (() => WriteT);
|
||||
|
||||
/**
|
||||
* An input signal can hold a directly bound value. This is used when e.g. Zone components
|
||||
* change input signal values.
|
||||
*/
|
||||
_boundValue?: WriteT;
|
||||
|
||||
bind(node: this, value: {value?: WriteT, computation?: () => WriteT}): void;
|
||||
|
||||
/** Transform function that is run whenever the node value is retrieved. */
|
||||
// TODO: do we need to memoize
|
||||
transform: (value: WriteT) => ReadT;
|
||||
}
|
||||
|
||||
// Note: Using an IIFE here to ensure that the spread assignment is not considered
|
||||
// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
|
||||
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
|
||||
export const INPUT_SIGNAL_NODE: InputSignalNode<unknown, unknown> = /* @__PURE__ */ (() => {
|
||||
return {
|
||||
...COMPUTED_NODE,
|
||||
|
||||
// Input-signal specific defaults.
|
||||
isInitialized: false,
|
||||
transform: value => value,
|
||||
|
||||
// Overrides from the computed node.
|
||||
// TODO: Should we change `ComputedNode` somehow to not rely on context (seems rather brittle).
|
||||
computation: function(this: InputSignalNode<unknown, unknown>) {
|
||||
if (!this.isInitialized) {
|
||||
// TODO: Make this a proper RuntimeError
|
||||
throw new Error(`InputSignal not yet initialized`);
|
||||
}
|
||||
|
||||
if (this._boundComputation !== undefined) {
|
||||
// TODO(signals): Do we need this?
|
||||
// Disable pure function memoization when running computations of input signals.
|
||||
// ---
|
||||
// Bound computations are generated with instructions in place to memoize allocations like
|
||||
// object literals, or for pipe transformations. Such operations do not need to be memoized
|
||||
// in input computations as the `InputSignal` naturally memoizes the whole expression.
|
||||
const prevPureFunctionsEnabled = setPureFunctionsEnabled(false);
|
||||
try {
|
||||
return this.transform(this._boundComputation());
|
||||
} finally {
|
||||
setPureFunctionsEnabled(prevPureFunctionsEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
// Alternatively, if there no bound computation, we use the bound value.
|
||||
return this.transform(this._boundValue);
|
||||
},
|
||||
|
||||
bind: function<ReadT, WriteT>(
|
||||
node: InputSignalNode<ReadT, WriteT>, value: {value?: WriteT, computation?: () => WriteT}) {
|
||||
// TODO(signals): perf]
|
||||
node._boundComputation = value.computation;
|
||||
node._boundValue = value.value;
|
||||
|
||||
// TODO: Do we need to switch between bound values/computation.
|
||||
node.dirty = true;
|
||||
node.value = UNSET;
|
||||
},
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {InputSignal, InputSignalNode} from './input_signal';
|
||||
import {WritableSignal} from './signal';
|
||||
|
||||
/**
|
||||
* A `Signal` representing a component or directive model input.
|
||||
*
|
||||
* Model inputs also have the `WritableSignal` interface for their WriteTer side.
|
||||
*/
|
||||
export type ModelSignal<ReadT, WriteT> =
|
||||
InputSignal<ReadT, WriteT>&Pick<WritableSignal<WriteT>, 'set'|'update'>;
|
||||
|
||||
export interface ModelSignalNode<ReadT, WriteT> extends InputSignalNode<ReadT, WriteT> {}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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';
|
||||
|
||||
export function output<T>(opts?: {alias?: string, isAsync?: boolean}): EventEmitter<T> {
|
||||
return new EventEmitter(opts?.isAsync);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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 {consumerMarkDirty, producerAccessed, producerUpdateValueVersion, REACTIVE_NODE, ReactiveNode, SIGNAL} from '@angular/core/primitives/signals';
|
||||
|
||||
import {ProviderToken} from '../../di/provider_token';
|
||||
import {QueryList} from '../../linker';
|
||||
import {QueryFlags} from '../interfaces/query';
|
||||
import {LView} from '../interfaces/view';
|
||||
import {createContentQueryInternal, createViewQueryInternal, loadQueryInternal, queryRefreshInternal} from '../query';
|
||||
import {getLView} from '../state';
|
||||
|
||||
import {Signal} from './api';
|
||||
|
||||
export interface QuerySignalNode<T> extends ReactiveNode {
|
||||
_lView?: LView;
|
||||
_queryIndex?: number;
|
||||
_queryList?: QueryList<T>;
|
||||
|
||||
bindToQuery(node: this, queryIndex: number): void;
|
||||
}
|
||||
|
||||
// Note: Using an IIFE here to ensure that the spread assignment is not considered
|
||||
// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
|
||||
// TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
|
||||
export const QUERY_SIGNAL_NODE: QuerySignalNode<unknown> = /* @__PURE__ */ (() => {
|
||||
return {
|
||||
...REACTIVE_NODE,
|
||||
|
||||
// Base reactive node.overrides
|
||||
producerMustRecompute: (node: QuerySignalNode<unknown>) => {
|
||||
return !!node._queryList?.dirty;
|
||||
},
|
||||
|
||||
producerRecomputeValue: (node: QuerySignalNode<unknown>) => {
|
||||
// The current value is stale. Check whether we need to produce a new one.
|
||||
// TODO: assert: I've got both the lView and queryIndex stored
|
||||
if (queryRefreshInternal(node._lView!, node._queryIndex!)) {
|
||||
node.version++;
|
||||
}
|
||||
},
|
||||
|
||||
// Query-specific implementations.
|
||||
bindToQuery: (node: QuerySignalNode<unknown>, queryIndex: number) => {
|
||||
// TODO: assert: should bind only once, make sure it is not re-assigned again
|
||||
node._lView = getLView();
|
||||
node._queryIndex = queryIndex;
|
||||
node._queryList = loadQueryInternal(node._lView, queryIndex);
|
||||
|
||||
node._queryList.onDirty(() => {
|
||||
console.error('Dirty');
|
||||
// Mark this producer as dirty and notify live consumer about the potential change. Note
|
||||
// that the onDirty callback will fire only on the initial dirty marking (that is,
|
||||
// subsequent dirty notifications are not fired- until the QueryList becomes clean again).
|
||||
consumerMarkDirty(node);
|
||||
});
|
||||
},
|
||||
|
||||
// TODO(signals): Unsubscribe - destroy?
|
||||
};
|
||||
})();
|
||||
|
||||
function querySignalFnFirst<T>(): Signal<T|undefined> {
|
||||
const node: QuerySignalNode<T> = Object.create(QUERY_SIGNAL_NODE);
|
||||
|
||||
function signalFn() {
|
||||
// Check if the value needs updating before returning it.
|
||||
producerUpdateValueVersion(node);
|
||||
|
||||
// Mark this producer as accessed.
|
||||
producerAccessed(node);
|
||||
|
||||
return node._queryList?.first;
|
||||
}
|
||||
(signalFn as any)[SIGNAL] = node;
|
||||
|
||||
return signalFn as Signal<T|undefined>;
|
||||
}
|
||||
|
||||
function querySignalFnAll<T>(): Signal<T[]> {
|
||||
const node: QuerySignalNode<T> = Object.create(QUERY_SIGNAL_NODE);
|
||||
|
||||
function signalFn() {
|
||||
// Check if the value needs updating before returning it.
|
||||
producerUpdateValueVersion(node);
|
||||
|
||||
// Mark this producer as accessed.
|
||||
producerAccessed(node);
|
||||
|
||||
return node._queryList?.toArray() ?? [];
|
||||
}
|
||||
(signalFn as any)[SIGNAL] = node;
|
||||
|
||||
return signalFn as Signal<T[]>;
|
||||
}
|
||||
|
||||
|
||||
// THINK: code duplication for predicate, flags etc.? Or would it be extracted by the compiler?
|
||||
export function ɵɵviewQueryCreate<T>(
|
||||
target: Signal<T|undefined>, predicate: ProviderToken<T>|string[], flags: QueryFlags,
|
||||
read?: any) {
|
||||
const lView = getLView();
|
||||
const reactiveQueryNode = target[SIGNAL] as QuerySignalNode<unknown>;
|
||||
reactiveQueryNode.bindToQuery(
|
||||
reactiveQueryNode, createViewQueryInternal<T>(lView, predicate, flags, read));
|
||||
}
|
||||
|
||||
// Q: assuming that the return type must be similar to InputSignal, with the write ability? (this is
|
||||
// needed only from the generated code so maybe not?)
|
||||
export function viewChild<T>(
|
||||
selector: ProviderToken<T>|string, opts?: {read?: any, static?: boolean}): Signal<T|undefined> {
|
||||
return querySignalFnFirst();
|
||||
}
|
||||
|
||||
export function viewChildren<T>(
|
||||
selector: ProviderToken<T>|string,
|
||||
opts?: {read?: any, emitDistinctChangesOnly?: boolean}): Signal<T[]> {
|
||||
// Q: by returning a signal we are effectively "dropping" QueryList from the public API. Is there
|
||||
// anything valuable there that we would be losing?
|
||||
return querySignalFnAll();
|
||||
}
|
||||
|
||||
export function ɵɵcontentQueryCreate<T>(
|
||||
target: Signal<T|undefined>, dirIndex: number, predicate: ProviderToken<T>|string[],
|
||||
flags: QueryFlags, read?: any) {
|
||||
const lView = getLView();
|
||||
const reactiveQueryNode = target[SIGNAL] as QuerySignalNode<unknown>;
|
||||
// Q: why do we need the directive index?
|
||||
reactiveQueryNode.bindToQuery(
|
||||
reactiveQueryNode, createContentQueryInternal<T>(lView, dirIndex, predicate, flags, read));
|
||||
}
|
||||
|
||||
export function contentChild<T>(
|
||||
selector: ProviderToken<T>|string,
|
||||
opts?: {descendants?: boolean, read?: any, static?: boolean}): Signal<T|undefined> {
|
||||
return querySignalFnFirst();
|
||||
}
|
||||
|
||||
export function contentChildren<T>(
|
||||
selector: ProviderToken<T>|string,
|
||||
opts?: {descendants?: boolean, read?: any, emitDistinctChangesOnly?: boolean}): Signal<T[]> {
|
||||
// Q: by returning a signal we are effectively "dropping" QueryList from the public API. Is there
|
||||
// anything valuable there that we would be loosing?
|
||||
return querySignalFnAll();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user