fix(ivy): match directives on namespaced elements (#33555)

Prior to this change, namespaced elements such as SVG elements would not
participate correctly in directive matching as their namespace was not
ignored, which was the case with the View Engine compiler. This led to
incorrect behavior at runtime and template type checking.

This commit resolved the issue by ignoring the namespace of elements and
attributes like they were in View Engine.

Fixes #32061

PR Close #33555
This commit is contained in:
JoostK
2019-11-03 12:12:35 +01:00
committed by Andrew Scott
parent 1ebe172c2e
commit bca437617f
4 changed files with 78 additions and 23 deletions

View File

@ -11,6 +11,7 @@ import {CssSelector, SelectorMatcher} from '../../selector';
import {BoundAttribute, BoundEvent, BoundText, Content, Element, Icu, Node, Reference, Template, Text, TextAttribute, Variable, Visitor} from '../r3_ast';
import {BoundTarget, DirectiveMeta, Target, TargetBinder} from './t2_api';
import {createCssSelector} from './template';
import {getAttrsForDirectiveMatching} from './util';
@ -222,25 +223,10 @@ class DirectiveBinder<DirectiveT extends DirectiveMeta> implements Visitor {
visitTemplate(template: Template): void { this.visitElementOrTemplate('ng-template', template); }
visitElementOrTemplate(tag: string, node: Element|Template): void {
visitElementOrTemplate(elementName: string, node: Element|Template): void {
// First, determine the HTML shape of the node for the purpose of directive matching.
// Do this by building up a `CssSelector` for the node.
const cssSelector = new CssSelector();
cssSelector.setElement(tag);
// Add attributes to the CSS selector.
const attrs = getAttrsForDirectiveMatching(node);
Object.getOwnPropertyNames(attrs).forEach((name) => {
const value = attrs[name];
cssSelector.addAttribute(name, value);
// Treat the 'class' attribute specially.
if (name.toLowerCase() === 'class') {
const classes = value.trim().split(/\s+/g);
classes.forEach(className => cssSelector.addClassName(className));
}
});
const cssSelector = createCssSelector(elementName, getAttrsForDirectiveMatching(node));
// Next, use the `SelectorMatcher` to get the list of directives on the node.
const directives: DirectiveT[] = [];

View File

@ -1189,9 +1189,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
return args;
}
private matchDirectives(tagName: string, elOrTpl: t.Element|t.Template) {
private matchDirectives(elementName: string, elOrTpl: t.Element|t.Template) {
if (this.directiveMatcher) {
const selector = createCssSelector(tagName, getAttrsForDirectiveMatching(elOrTpl));
const selector = createCssSelector(elementName, getAttrsForDirectiveMatching(elOrTpl));
this.directiveMatcher.match(
selector, (cssSelector, staticType) => { this.directives.add(staticType); });
}
@ -1762,15 +1762,18 @@ export class BindingScope implements LocalResolver {
/**
* Creates a `CssSelector` given a tag name and a map of attributes
*/
function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector {
export function createCssSelector(
elementName: string, attributes: {[name: string]: string}): CssSelector {
const cssSelector = new CssSelector();
const elementNameNoNs = splitNsName(elementName)[1];
cssSelector.setElement(tag);
cssSelector.setElement(elementNameNoNs);
Object.getOwnPropertyNames(attributes).forEach((name) => {
const nameNoNs = splitNsName(name)[1];
const value = attributes[name];
cssSelector.addAttribute(name, value);
cssSelector.addAttribute(nameNoNs, value);
if (name.toLowerCase() === 'class') {
const classes = value.trim().split(/\s+/);
classes.forEach(className => cssSelector.addClassName(className));