diff --git a/modules/angular2/src/core/compiler/compiler.ts b/modules/angular2/src/core/compiler/compiler.ts index cf772db5ed2c1..f1292c845f894 100644 --- a/modules/angular2/src/core/compiler/compiler.ts +++ b/modules/angular2/src/core/compiler/compiler.ts @@ -153,8 +153,8 @@ export class Compiler { } } - var boundDirectives = - ListWrapper.map(directives, (directive) => this._bindDirective(directive)); + var boundDirectives = this._removeDuplicatedDirectives( + ListWrapper.map(directives, (directive) => this._bindDirective(directive))); var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives); pvPromise = @@ -167,6 +167,12 @@ export class Compiler { return pvPromise; } + private _removeDuplicatedDirectives(directives: List): List { + var directivesMap: Map = new Map(); + directives.forEach((dirBinding) => { directivesMap.set(dirBinding.key.id, dirBinding); }); + return MapWrapper.values(directivesMap); + } + private _compileNestedProtoViews(componentBinding, renderPv, directives): Promise| AppProtoView { var protoViews = diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 75f629ad12ab5..5638fd7098044 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -299,6 +299,40 @@ export function main() { .then((rootTC) => { async.done(); }); })); + it('should execute a given directive once, even if specified multiple times', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MyComp, new viewAnn.View({ + template: '

', + directives: [ + DuplicateDir, + DuplicateDir, + [DuplicateDir, [DuplicateDir, bind(DuplicateDir).toClass(DuplicateDir)]] + ] + })) + .createAsync(MyComp) + .then((rootTC) => { + expect(rootTC.nativeElement).toHaveText('noduplicate'); + async.done(); + }); + })); + + it('should use the last directive binding per directive', + inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MyComp, new viewAnn.View({ + template: '

', + directives: [ + bind(DuplicateDir) + .toClass(DuplicateDir), + bind(DuplicateDir).toClass(OtherDuplicateDir) + ] + })) + .createAsync(MyComp) + .then((rootTC) => { + expect(rootTC.nativeElement).toHaveText('othernoduplicate'); + async.done(); + }); + })); + it('should support directives where a selector matches property binding', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MyComp, new viewAnn.View( @@ -1805,3 +1839,17 @@ class ExportDir { @Component({selector: 'comp'}) class ComponentWithoutView { } + +@Directive({selector: '[no-duplicate]'}) +class DuplicateDir { + constructor(renderer: DomRenderer, private elRef: ElementRef) { + DOM.setText(elRef.nativeElement, DOM.getText(elRef.nativeElement) + 'noduplicate'); + } +} + +@Directive({selector: '[no-duplicate]'}) +class OtherDuplicateDir { + constructor(renderer: DomRenderer, private elRef: ElementRef) { + DOM.setText(elRef.nativeElement, DOM.getText(elRef.nativeElement) + 'othernoduplicate'); + } +}