Skip to content

Commit eacc8e3

Browse files
committed
fix(compiler): merge class and style attributes from the element with the host attributes
Closes angular#4583 Closes angular#4680
1 parent da1272f commit eacc8e3

File tree

2 files changed

+79
-5
lines changed

2 files changed

+79
-5
lines changed

modules/angular2/src/core/compiler/command_compiler.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {isPresent, isBlank, Type, isString} from 'angular2/src/core/facade/lang';
1+
import {isPresent, isBlank, Type, isString, StringWrapper} from 'angular2/src/core/facade/lang';
22
import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
33
import {
44
TemplateCmd,
@@ -44,6 +44,8 @@ export var TEMPLATE_COMMANDS_MODULE_REF =
4444
moduleRef(`package:angular2/src/core/linker/template_commands${MODULE_SUFFIX}`);
4545

4646
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
47+
const CLASS_ATTR = 'class';
48+
const STYLE_ATTR = 'style';
4749

4850
@Injectable()
4951
export class CommandCompiler {
@@ -211,14 +213,14 @@ class CommandBuilderVisitor<R> implements TemplateAstVisitor {
211213

212214
private _readAttrNameAndValues(directives: CompileDirectiveMetadata[],
213215
attrAsts: TemplateAst[]): string[] {
214-
var attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []);
216+
var attrs = keyValueArrayToMap(visitAndReturnContext(this, attrAsts, []));
215217
directives.forEach(directiveMeta => {
216218
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
217-
attrNameAndValues.push(name);
218-
attrNameAndValues.push(value);
219+
var prevValue = attrs[name];
220+
attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
219221
});
220222
});
221-
return removeKeyValueArrayDuplicates(attrNameAndValues);
223+
return mapToKeyValueArray(attrs);
222224
}
223225

224226
visitNgContent(ast: NgContentAst, context: any): any {
@@ -328,6 +330,36 @@ function removeKeyValueArrayDuplicates(keyValueArray: string[]): string[] {
328330
return resultKeyValueArray;
329331
}
330332

333+
function keyValueArrayToMap(keyValueArr: string[]): {[key: string]: string} {
334+
var data: {[key: string]: string} = {};
335+
for (var i = 0; i < keyValueArr.length; i += 2) {
336+
data[keyValueArr[i]] = keyValueArr[i + 1];
337+
}
338+
return data;
339+
}
340+
341+
function mapToKeyValueArray(data: {[key: string]: string}): string[] {
342+
var entryArray = [];
343+
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
344+
// We need to sort to get a defined output order
345+
// for tests and for caching generated artifacts...
346+
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
347+
var keyValueArray = [];
348+
entryArray.forEach((entry) => {
349+
keyValueArray.push(entry[0]);
350+
keyValueArray.push(entry[1]);
351+
});
352+
return keyValueArray;
353+
}
354+
355+
function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string {
356+
if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) {
357+
return `${attrValue1} ${attrValue2}`;
358+
} else {
359+
return attrValue2;
360+
}
361+
}
362+
331363
class DirectiveContext {
332364
constructor(public index: number, public eventTargetAndNames: string[],
333365
public targetVariableNameAndValues: any[],

modules/angular2/test/core/compiler/command_compiler_spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,48 @@ export function main() {
218218
});
219219
}));
220220

221+
it('should merge element attributes with host attributes',
222+
inject([AsyncTestCompleter], (async) => {
223+
var rootComp = createComp({
224+
type: RootCompTypeMeta,
225+
template: '<div class="origclass" style="origstyle" role="origrole" attr1>'
226+
});
227+
var dir = CompileDirectiveMetadata.create({
228+
selector: 'div',
229+
isComponent: false,
230+
type: SomeDirTypeMeta,
231+
host: {'class': 'newclass', 'style': 'newstyle', 'role': 'newrole', 'attr2': ''}
232+
});
233+
run(rootComp, [dir])
234+
.then((data) => {
235+
expect(data).toEqual([
236+
[
237+
BEGIN_ELEMENT,
238+
'div',
239+
[
240+
'attr1',
241+
'',
242+
'attr2',
243+
'',
244+
'class',
245+
'origclass newclass',
246+
'role',
247+
'newrole',
248+
'style',
249+
'origstyle newstyle'
250+
],
251+
[],
252+
[],
253+
['SomeDirType'],
254+
true,
255+
null
256+
],
257+
[END_ELEMENT]
258+
]);
259+
async.done();
260+
});
261+
}));
262+
221263
it('should emulate style encapsulation', inject([AsyncTestCompleter], (async) => {
222264
var rootComp = createComp({
223265
type: RootCompTypeMeta,

0 commit comments

Comments
 (0)