fix(ivy): trigger directive inputs with i18n translations (#30402)

Changed runtime i18n to define attributes with bindings, or matching directive inputs/outputs as element properties as we are supposed to do in Angular.
This PR fixes the issue where directive inputs wouldn't be trigged.

FW-1315 #resolve
PR Close #30402
This commit is contained in:
Olivier Combe
2019-05-15 14:27:07 +02:00
committed by Matias Niemelä
parent 41f372fe79
commit 91699259b2
5 changed files with 105 additions and 48 deletions

View File

@ -15,8 +15,8 @@ import {addAllToArray} from '../util/array_utils';
import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
import {attachPatchData} from './context_discovery';
import {attachI18nOpCodesDebug} from './debug';
import {ɵɵelementAttribute, ɵɵload, ɵɵtextBinding} from './instructions/all';
import {allocExpando, getOrCreateTNode} from './instructions/shared';
import {elementAttributeInternal, ɵɵload, ɵɵtextBinding} from './instructions/all';
import {allocExpando, elementPropertyInternal, getOrCreateTNode, setInputsForProperty} from './instructions/shared';
import {LContainer, NATIVE} from './interfaces/container';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n';
import {TElementNode, TIcuContainerNode, TNode, TNodeType} from './interfaces/node';
@ -30,6 +30,7 @@ import {NO_CHANGE} from './tokens';
import {renderStringify} from './util/misc_utils';
import {getNativeByIndex, getNativeByTNode, getTNode, isLContainer} from './util/view_utils';
const MARKER = `<EFBFBD>`;
const ICU_BLOCK_REGEXP = /^\s*(<28>\d+:?\d*<2A>)\s*,\s*(select|plural)\s*,/;
const SUBTEMPLATE_REGEXP = /<2F>\/?\*(\d+:\d+)<29>/gi;
@ -735,7 +736,10 @@ function readCreateOpCodes(
const elementNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
const attrName = createOpCodes[++i] as string;
const attrValue = createOpCodes[++i] as string;
ɵɵelementAttribute(elementNodeIndex, attrName, attrValue);
const renderer = viewData[RENDERER];
// This code is used for ICU expressions only, since we don't support
// directives/components in ICUs, we don't need to worry about inputs here
elementAttributeInternal(elementNodeIndex, attrName, attrValue, viewData, renderer);
break;
default:
throw new Error(`Unable to determine the type of mutate operation for "${opCode}"`);
@ -810,9 +814,9 @@ function readUpdateOpCodes(
let icuTNode: TIcuContainerNode;
switch (opCode & I18nUpdateOpCode.MASK_OPCODE) {
case I18nUpdateOpCode.Attr:
const attrName = updateOpCodes[++j] as string;
const propName = updateOpCodes[++j] as string;
const sanitizeFn = updateOpCodes[++j] as SanitizerFn | null;
ɵɵelementAttribute(nodeIndex, attrName, value, sanitizeFn);
elementPropertyInternal(nodeIndex, propName, value, sanitizeFn);
break;
case I18nUpdateOpCode.Text:
ɵɵtextBinding(nodeIndex, value);
@ -954,6 +958,7 @@ function i18nAttributesFirstPass(tView: TView, index: number, values: string[])
if (j & 1) {
// Odd indexes are ICU expressions
// TODO(ocombe): support ICU expressions in attributes
throw new Error('ICU expressions are not yet supported in attributes');
} else if (value !== '') {
// Even indexes are text (including bindings)
const hasBinding = !!value.match(BINDING_REGEXP);
@ -961,7 +966,15 @@ function i18nAttributesFirstPass(tView: TView, index: number, values: string[])
addAllToArray(
generateBindingUpdateOpCodes(value, previousElementIndex, attrName), updateOpCodes);
} else {
ɵɵelementAttribute(previousElementIndex, attrName, value);
const lView = getLView();
const renderer = lView[RENDERER];
elementAttributeInternal(previousElementIndex, attrName, value, lView, renderer);
// Check if that attribute is a directive input
const tNode = getTNode(previousElementIndex, lView);
const dataValue = tNode.inputs && tNode.inputs[attrName];
if (dataValue) {
setInputsForProperty(lView, dataValue, value);
}
}
}
}

View File

@ -11,10 +11,10 @@ import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
import {TAttributes, TNodeFlags, TNodeType} from '../interfaces/node';
import {RElement, isProceduralRenderer} from '../interfaces/renderer';
import {RElement, Renderer3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {StylingContext} from '../interfaces/styling';
import {BINDING_INDEX, HEADER_OFFSET, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {BINDING_INDEX, HEADER_OFFSET, LView, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util';
@ -27,12 +27,10 @@ import {NO_CHANGE} from '../tokens';
import {attrsStylingIndexOf, setUpAttributes} from '../util/attrs_utils';
import {renderStringify} from '../util/misc_utils';
import {getNativeByIndex, getNativeByTNode, getTNode} from '../util/view_utils';
import {createDirectivesAndLocals, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, setInputsForProperty, setNodeStylingTemplate} from './shared';
import {getActiveDirectiveStylingIndex} from './styling';
/**
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
*
@ -202,7 +200,7 @@ export function ɵɵelement(
/**
* Updates the value or removes an attribute on an Element.
*
* @param number index The index of the element in the data array
* @param index The index of the element in the data array
* @param name name The name of the attribute.
* @param value value The attribute is removed when value is `null` or `undefined`.
* Otherwise the attribute value is set to the stringified value.
@ -215,27 +213,33 @@ export function ɵɵelementAttribute(
index: number, name: string, value: any, sanitizer?: SanitizerFn | null,
namespace?: string): void {
if (value !== NO_CHANGE) {
ngDevMode && validateAgainstEventAttributes(name);
const lView = getLView();
const renderer = lView[RENDERER];
const element = getNativeByIndex(index, lView) as RElement;
if (value == null) {
ngDevMode && ngDevMode.rendererRemoveAttribute++;
isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) :
element.removeAttribute(name);
elementAttributeInternal(index, name, value, lView, renderer, sanitizer, namespace);
}
}
export function elementAttributeInternal(
index: number, name: string, value: any, lView: LView, renderer: Renderer3,
sanitizer?: SanitizerFn | null, namespace?: string) {
ngDevMode && validateAgainstEventAttributes(name);
const element = getNativeByIndex(index, lView) as RElement;
if (value == null) {
ngDevMode && ngDevMode.rendererRemoveAttribute++;
isProceduralRenderer(renderer) ? renderer.removeAttribute(element, name, namespace) :
element.removeAttribute(name);
} else {
ngDevMode && ngDevMode.rendererSetAttribute++;
const tNode = getTNode(index, lView);
const strValue =
sanitizer == null ? renderStringify(value) : sanitizer(value, tNode.tagName || '', name);
if (isProceduralRenderer(renderer)) {
renderer.setAttribute(element, name, strValue, namespace);
} else {
ngDevMode && ngDevMode.rendererSetAttribute++;
const tNode = getTNode(index, lView);
const strValue =
sanitizer == null ? renderStringify(value) : sanitizer(value, tNode.tagName || '', name);
if (isProceduralRenderer(renderer)) {
renderer.setAttribute(element, name, strValue, namespace);
} else {
namespace ? element.setAttributeNS(namespace, name, strValue) :
element.setAttribute(name, strValue);
}
namespace ? element.setAttributeNS(namespace, name, strValue) :
element.setAttribute(name, strValue);
}
}
}