\]unknown="{{ctxProp}}"><\/div>"\): .*MyComp.html@0:5/);
}
});
onlyInIvy('Unknown property logs an error message instead of throwing')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = '
';
TestBed.overrideComponent(MyComp, {set: {template}});
const spy = spyOn(console, 'error');
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'unknown' since it isn't a known property of 'div'./);
});
modifiedInIvy('Unknown property error thrown instead of logging it')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]});
const template = '
{{item}}
';
TestBed.overrideComponent(MyComp, {set: {template}});
try {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
throw 'Should throw';
} catch (e) {
expect(e.message).toMatch(
/Can't bind to 'ngForIn' since it isn't a known property of 'div'./);
}
});
onlyInIvy('Unknown property logs an error message instead of throwing it')
.it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({imports: [CommonModule], declarations: [MyComp]});
const template = '
{{item}}
';
TestBed.overrideComponent(MyComp, {set: {template}});
const spy = spyOn(console, 'error');
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'ngForIn' since it isn't a known property of 'div'./);
});
it('should not throw for property binding to a non-existing property when there is a matching directive property',
() => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
const template = '
';
TestBed.overrideComponent(MyComp, {set: {template}});
expect(() => TestBed.createComponent(MyComp)).not.toThrow();
});
it('should not be created when there is a directive with the same property', () => {
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTitle]});
const template = '
';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = 'TITLE';
fixture.detectChanges();
const el = fixture.nativeElement.querySelector('span');
expect(el.title).toBeFalsy();
});
it('should work when a directive uses hostProperty to update the DOM element', () => {
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTitleAndHostProperty]});
const template = '
';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = 'TITLE';
fixture.detectChanges();
const el = fixture.nativeElement.querySelector('span');
expect(getDOM().getProperty(el, 'title')).toEqual('TITLE');
});
});
describe('logging property updates', () => {
it('should reflect property values as attributes', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
TestBed.overrideComponent(
MyComp, {set: {template: `
`}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = 'hello';
fixture.detectChanges();
const html = fixture.nativeElement.innerHTML;
expect(html).toContain('ng-reflect-dir-prop="hello"');
});
it('should reflect property values on unbound inputs', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
TestBed.overrideComponent(
MyComp, {set: {template: `
`}});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const html = fixture.nativeElement.innerHTML;
expect(html).toContain('ng-reflect-dir-prop="hello"');
expect(html).not.toContain('ng-reflect-title');
});
it(`should work with prop names containing '$'`, () => {
TestBed.configureTestingModule({declarations: [ParentCmp, SomeCmpWithInput]});
const fixture = TestBed.createComponent(ParentCmp);
fixture.detectChanges();
const html = fixture.nativeElement.innerHTML;
expect(html).toContain('ng-reflect-test_="hello"');
});
it('should reflect property values on template comments', () => {
const fixture =
TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent(
MyComp, {set: {template: `
`}})
.createComponent(MyComp);
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
const html = fixture.nativeElement.innerHTML;
expect(html).toContain('"ng-reflect-ng-if": "true"');
});
it('should reflect property values on ng-containers', () => {
const fixture =
TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent(
MyComp,
{set: {template: `
content `}})
.createComponent(MyComp);
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
const html = fixture.nativeElement.innerHTML;
expect(html).toContain('"ng-reflect-ng-if": "true"');
});
it('should reflect property values of multiple directive bound to the same input name',
() => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]});
TestBed.overrideComponent(
MyComp, {set: {template: `
`}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = 'hello';
fixture.detectChanges();
const html = fixture.nativeElement.innerHTML;
expect(html).toContain('ng-reflect-dir-prop="hello"');
expect(html).toContain('ng-reflect-dir-prop2="hello"');
});
it('should indicate when toString() throws', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
const template = '
';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('[ERROR]');
});
it('should not reflect undefined values', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]});
TestBed.overrideComponent(
MyComp, {set: {template: `
`}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = 'hello';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('ng-reflect-dir-prop="hello"');
fixture.componentInstance.ctxProp = undefined!;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).not.toContain('ng-reflect-');
});
it('should not reflect null values', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]});
TestBed.overrideComponent(
MyComp, {set: {template: `
`}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = 'hello';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('ng-reflect-dir-prop="hello"');
fixture.componentInstance.ctxProp = null!;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).not.toContain('ng-reflect-');
});
it('should reflect empty strings', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyDir2]});
TestBed.overrideComponent(
MyComp, {set: {template: `
`}});
const fixture = TestBed.createComponent(MyComp);
fixture.componentInstance.ctxProp = '';
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('ng-reflect-dir-prop=""');
});
it('should not reflect in comment nodes when the value changes to undefined', () => {
const fixture =
TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent(
MyComp, {set: {template: `
`}})
.createComponent(MyComp);
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
let html = fixture.nativeElement.innerHTML;
expect(html).toContain('bindings={');
expect(html).toContain('"ng-reflect-ng-if": "true"');
fixture.componentInstance.ctxBoolProp = undefined!;
fixture.detectChanges();
html = fixture.nativeElement.innerHTML;
expect(html).toContain('bindings={');
expect(html).not.toContain('ng-reflect');
});
it('should reflect in comment nodes when the value changes to null', () => {
const fixture =
TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent(
MyComp, {set: {template: `
`}})
.createComponent(MyComp);
fixture.componentInstance.ctxBoolProp = true;
fixture.detectChanges();
let html = fixture.nativeElement.innerHTML;
expect(html).toContain('bindings={');
expect(html).toContain('"ng-reflect-ng-if": "true"');
fixture.componentInstance.ctxBoolProp = null!;
fixture.detectChanges();
html = fixture.nativeElement.innerHTML;
expect(html).toContain('bindings={');
expect(html).toContain('"ng-reflect-ng-if": null');
});
});
describe('property decorators', () => {
it('should support property decorators', () => {
TestBed.configureTestingModule({
declarations: [MyComp, DirectiveWithPropDecorators],
schemas: [NO_ERRORS_SCHEMA],
});
const template = '
';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const dir = fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators);
expect(dir.dirProp).toEqual('aaa');
});
it('should support host binding decorators', () => {
TestBed.configureTestingModule({
declarations: [MyComp, DirectiveWithPropDecorators],
schemas: [NO_ERRORS_SCHEMA],
});
const template = '
';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const dir = fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators);
dir.myAttr = 'aaa';
fixture.detectChanges();
expect(fixture.debugElement.children[0].nativeElement.outerHTML).toContain('my-attr="aaa"');
});
if (getDOM().supportsDOMEvents()) {
it('should support event decorators', fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [MyComp, DirectiveWithPropDecorators],
schemas: [NO_ERRORS_SCHEMA],
});
const template = `
`;
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
tick();
const emitter =
fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators);
emitter.fireEvent('fired !');
tick();
expect(fixture.componentInstance.ctxProp).toEqual('called');
}));
it('should support host listener decorators', () => {
TestBed.configureTestingModule({
declarations: [MyComp, DirectiveWithPropDecorators],
schemas: [NO_ERRORS_SCHEMA],
});
const template = ' ';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const dir = fixture.debugElement.children[0].injector.get(DirectiveWithPropDecorators);
const native = fixture.debugElement.children[0].nativeElement;
getDOM().dispatchEvent(native, createMouseEvent('click'));
expect(dir.target).toBe(native);
});
}
it('should support defining views in the component decorator', () => {
TestBed.configureTestingModule({
declarations: [MyComp, ComponentWithTemplate],
imports: [CommonModule],
schemas: [NO_ERRORS_SCHEMA],
});
const template = ' ';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const native = fixture.debugElement.children[0].nativeElement;
expect(native).toHaveText('No View Decorator: 123');
});
});
describe('whitespaces in templates', () => {
it('should not remove whitespaces by default', async(() => {
@Component({
selector: 'comp',
template: 'foo bar ',
})
class MyCmp {
}
const f = TestBed.configureTestingModule({declarations: [MyCmp]}).createComponent(MyCmp);
f.detectChanges();
expect(f.nativeElement.childNodes.length).toBe(2);
}));
it('should not remove whitespaces when explicitly requested not to do so', async(() => {
@Component({
selector: 'comp',
template: 'foo bar ',
preserveWhitespaces: true,
})
class MyCmp {
}
const f = TestBed.configureTestingModule({declarations: [MyCmp]}).createComponent(MyCmp);
f.detectChanges();
expect(f.nativeElement.childNodes.length).toBe(3);
}));
it('should remove whitespaces when explicitly requested to do so', async(() => {
@Component({
selector: 'comp',
template: 'foo bar ',
preserveWhitespaces: false,
})
class MyCmp {
}
const f = TestBed.configureTestingModule({declarations: [MyCmp]}).createComponent(MyCmp);
f.detectChanges();
expect(f.nativeElement.childNodes.length).toBe(2);
}));
});
if (getDOM().supportsDOMEvents()) {
describe('svg', () => {
it('should support svg elements', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template = ' ';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const el = fixture.nativeElement;
const svg = el.childNodes[0];
const use = svg.childNodes[0];
expect(getDOM().getProperty(svg, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg');
expect(getDOM().getProperty(use, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg');
const firstAttribute = getDOM().getProperty(use, 'attributes')[0];
expect(firstAttribute.name).toEqual('xlink:href');
expect(firstAttribute.namespaceURI).toEqual('http://www.w3.org/1999/xlink');
});
it('should support foreignObjects with document fragments', () => {
TestBed.configureTestingModule({declarations: [MyComp]});
const template =
'Test
';
TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp);
const el = fixture.nativeElement;
const svg = el.childNodes[0];
const foreignObject = svg.childNodes[0];
const p = foreignObject.childNodes[0];
expect(getDOM().getProperty(svg, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg');
expect(getDOM().getProperty(foreignObject, 'namespaceURI'))
.toEqual('http://www.w3.org/2000/svg');
expect(getDOM().getProperty(p, 'namespaceURI'))
.toEqual('http://www.w3.org/1999/xhtml');
});
});
describe('attributes', () => {
it('should support attributes with namespace', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
const template = ' ';
TestBed.overrideComponent(SomeCmp, {set: {template}});
const fixture = TestBed.createComponent(SomeCmp);
const useEl = fixture.nativeElement.firstChild;
expect(useEl.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toEqual('#id');
});
it('should support binding to attributes with namespace', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeCmp]});
const template = ' ';
TestBed.overrideComponent(SomeCmp, {set: {template}});
const fixture = TestBed.createComponent(SomeCmp);
const cmp = fixture.componentInstance;
const useEl = fixture.nativeElement.firstChild;
cmp.value = '#id';
fixture.detectChanges();
expect(useEl.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toEqual('#id');
cmp.value = null;
fixture.detectChanges();
expect(useEl.hasAttributeNS('http://www.w3.org/1999/xlink', 'href')).toEqual(false);
});
});
}
});
}
@Component({selector: 'cmp-with-default-interpolation', template: `{{text}}`})
class ComponentWithDefaultInterpolation {
text = 'Default Interpolation';
}
@Component({
selector: 'cmp-with-custom-interpolation-a',
template: `{%text%}
`,
interpolation: ['{%', '%}']
})
class ComponentWithCustomInterpolationA {
text = 'Custom Interpolation A';
}
@Component({
selector: 'cmp-with-custom-interpolation-b',
template:
`{**text%}
( )`,
interpolation: ['{**', '%}']
})
class ComponentWithCustomInterpolationB {
text = 'Custom Interpolation B';
}
@Injectable()
class MyService {
greeting: string;
constructor() {
this.greeting = 'hello';
}
}
@Component({selector: 'simple-imp-cmp', template: ''})
class SimpleImperativeViewComponent {
done: any;
constructor(self: ElementRef) {
const hostElement = self.nativeElement;
hostElement.appendChild(el('hello imp view'));
}
}
@Directive({selector: 'dynamic-vp'})
class DynamicViewport {
private componentFactory: ComponentFactory;
private injector: Injector;
constructor(private vc: ViewContainerRef, componentFactoryResolver: ComponentFactoryResolver) {
const myService = new MyService();
myService.greeting = 'dynamic greet';
this.injector = Injector.create([{provide: MyService, useValue: myService}], vc.injector);
this.componentFactory =
componentFactoryResolver.resolveComponentFactory(ChildCompUsingService)!;
}
create(): ComponentRef {
return this.vc.createComponent(this.componentFactory, this.vc.length, this.injector);
}
insert(viewRef: ViewRef, index?: number): ViewRef {
return this.vc.insert(viewRef, index);
}
move(viewRef: ViewRef, currentIndex: number): ViewRef {
return this.vc.move(viewRef, currentIndex);
}
}
@Directive({selector: '[my-dir]', inputs: ['dirProp: elprop'], exportAs: 'mydir'})
class MyDir {
dirProp: string;
constructor() {
this.dirProp = '';
}
}
@Directive({selector: '[my-dir2]', inputs: ['dirProp2: elprop'], exportAs: 'mydir2'})
class MyDir2 {
dirProp2: string;
constructor() {
this.dirProp2 = '';
}
}
@Directive({selector: '[title]', inputs: ['title']})
class DirectiveWithTitle {
// TODO(issue/24571): remove '!'.
title!: string;
}
@Directive({selector: '[title]', inputs: ['title'], host: {'[title]': 'title'}})
class DirectiveWithTitleAndHostProperty {
// TODO(issue/24571): remove '!'.
title!: string;
}
@Component({selector: 'event-cmp', template: '
'})
class EventCmp {
noop() {}
}
@Component({
selector: 'push-cmp',
inputs: ['prop'],
host: {'(click)': 'true'},
changeDetection: ChangeDetectionStrategy.OnPush,
template:
'{{field}}
'
})
class PushCmp {
numberOfChecks: number;
prop: any;
constructor() {
this.numberOfChecks = 0;
}
noop() {}
get field() {
this.numberOfChecks++;
return 'fixed';
}
}
@Component({
selector: 'push-cmp-with-ref',
inputs: ['prop'],
changeDetection: ChangeDetectionStrategy.OnPush,
template: '{{field}}'
})
class PushCmpWithRef {
numberOfChecks: number;
ref: ChangeDetectorRef;
prop: any;
constructor(ref: ChangeDetectorRef) {
this.numberOfChecks = 0;
this.ref = ref;
}
get field() {
this.numberOfChecks++;
return 'fixed';
}
propagate() {
this.ref.markForCheck();
}
}
@Component({
selector: 'push-cmp-with-host-event',
host: {'(click)': 'ctxCallback($event)'},
changeDetection: ChangeDetectionStrategy.OnPush,
template: ''
})
class PushCmpWithHostEvent {
ctxCallback: Function = (_: any) => {};
}
@Component({
selector: 'push-cmp-with-async',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '{{field | async}}'
})
class PushCmpWithAsyncPipe {
numberOfChecks: number = 0;
// TODO(issue/24571): remove '!'.
resolve!: (result: any) => void;
promise: Promise;
constructor() {
this.promise = new Promise((resolve) => {
this.resolve = resolve;
});
}
get field() {
this.numberOfChecks++;
return this.promise;
}
}
@Component({selector: 'my-comp', template: ''})
class MyComp {
ctxProp: string;
ctxNumProp: number;
ctxBoolProp: boolean;
ctxArrProp: number[];
toStringThrow = {
toString: function() {
throw 'boom';
}
};
constructor() {
this.ctxProp = 'initial value';
this.ctxNumProp = 0;
this.ctxBoolProp = false;
this.ctxArrProp = [0, 1, 2];
}
throwError() {
throw 'boom';
}
}
@Component({
selector: 'child-cmp',
inputs: ['dirProp'],
viewProviders: [MyService],
template: '{{ctxProp}}'
})
class ChildComp {
ctxProp: string;
dirProp: string|null;
constructor(service: MyService) {
this.ctxProp = service.greeting;
this.dirProp = null;
}
}
@Component({selector: 'child-cmp-no-template', template: ''})
class ChildCompNoTemplate {
ctxProp: string = 'hello';
}
@Component({selector: 'child-cmp-svc', template: '{{ctxProp}}'})
class ChildCompUsingService {
ctxProp: string;
constructor(service: MyService) {
this.ctxProp = service.greeting;
}
}
@Directive({selector: 'some-directive'})
class SomeDirective {
}
class SomeDirectiveMissingAnnotation {}
@Component({
selector: 'cmp-with-host',
template: 'Component with an injected host
',
})
class CompWithHost {
myHost: SomeDirective;
constructor(@Host() someComp: SomeDirective) {
this.myHost = someComp;
}
}
@Component({selector: '[child-cmp2]', viewProviders: [MyService]})
class ChildComp2 {
ctxProp: string;
dirProp: string|null;
constructor(service: MyService) {
this.ctxProp = service.greeting;
this.dirProp = null;
}
}
class SomeViewportContext {
constructor(public someTmpl: string) {}
}
@Directive({selector: '[some-viewport]'})
class SomeViewport {
constructor(public container: ViewContainerRef, templateRef: TemplateRef) {
container.createEmbeddedView(templateRef, new SomeViewportContext('hello'));
container.createEmbeddedView(templateRef, new SomeViewportContext('again'));
}
}
@Directive({selector: '[pollutedContext]'})
class PollutedContext {
constructor(private tplRef: TemplateRef, private vcRef: ViewContainerRef) {
const evRef = this.vcRef.createEmbeddedView(this.tplRef);
evRef.context.bar = 'baz';
}
}
@Directive({selector: '[noContext]'})
class NoContext {
constructor(private tplRef: TemplateRef, private vcRef: ViewContainerRef) {
this.vcRef.createEmbeddedView(this.tplRef);
}
}
@Pipe({name: 'double'})
class DoublePipe implements PipeTransform, OnDestroy {
ngOnDestroy() {}
transform(value: any) {
return `${value}${value}`;
}
}
@Directive({selector: '[emitter]', outputs: ['event']})
class DirectiveEmittingEvent {
msg: string;
event: EventEmitter;
constructor() {
this.msg = '';
this.event = new EventEmitter();
}
fireEvent(msg: string) {
this.event.emit(msg);
}
}
@Directive({selector: '[update-host-attributes]', host: {'role': 'button'}})
class DirectiveUpdatingHostAttributes {
}
@Directive({selector: '[update-host-properties]', host: {'[id]': 'id'}})
class DirectiveUpdatingHostProperties {
id: string;
constructor() {
this.id = 'one';
}
}
@Directive({selector: '[listener]', host: {'(event)': 'onEvent($event)'}})
class DirectiveListeningEvent {
msg: string;
constructor() {
this.msg = '';
}
onEvent(msg: string) {
this.msg = msg;
}
}
@Directive({
selector: '[listener]',
host: {
'(domEvent)': 'onEvent($event.type)',
'(window:domEvent)': 'onWindowEvent($event.type)',
'(document:domEvent)': 'onDocumentEvent($event.type)',
'(body:domEvent)': 'onBodyEvent($event.type)'
}
})
class DirectiveListeningDomEvent {
eventTypes: string[] = [];
onEvent(eventType: string) {
this.eventTypes.push(eventType);
}
onWindowEvent(eventType: string) {
this.eventTypes.push('window_' + eventType);
}
onDocumentEvent(eventType: string) {
this.eventTypes.push('document_' + eventType);
}
onBodyEvent(eventType: string) {
this.eventTypes.push('body_' + eventType);
}
}
let globalCounter = 0;
@Directive({selector: '[listenerother]', host: {'(window:domEvent)': 'onEvent($event.type)'}})
class DirectiveListeningDomEventOther {
eventType: string;
constructor() {
this.eventType = '';
}
onEvent(eventType: string) {
globalCounter++;
this.eventType = 'other_' + eventType;
}
}
@Directive({selector: '[listenerprevent]', host: {'(click)': 'onEvent($event)'}})
class DirectiveListeningDomEventPrevent {
onEvent(event: any) {
return false;
}
}
@Directive({selector: '[listenernoprevent]', host: {'(click)': 'onEvent($event)'}})
class DirectiveListeningDomEventNoPrevent {
onEvent(event: any) {
return true;
}
}
@Directive({selector: '[id]', inputs: ['id']})
class IdDir {
// TODO(issue/24571): remove '!'.
id!: string;
}
@Directive({selector: '[customEvent]'})
class EventDir {
@Output() customEvent = new EventEmitter();
doSomething() {}
}
@Directive({selector: '[static]'})
class NeedsAttribute {
typeAttribute: string;
staticAttribute: string;
fooAttribute: string;
constructor(
@Attribute('type') typeAttribute: string, @Attribute('static') staticAttribute: string,
@Attribute('foo') fooAttribute: string) {
this.typeAttribute = typeAttribute;
this.staticAttribute = staticAttribute;
this.fooAttribute = fooAttribute;
}
}
@Injectable()
class PublicApi {
}
@Directive({
selector: '[public-api]',
providers: [{provide: PublicApi, useExisting: PrivateImpl, deps: []}]
})
class PrivateImpl extends PublicApi {
}
@Directive({selector: '[needs-public-api]'})
class NeedsPublicApi {
constructor(@Host() api: PublicApi) {
expect(api instanceof PrivateImpl).toBe(true);
}
}
class ToolbarContext {
constructor(public toolbarProp: string) {}
}
@Directive({selector: '[toolbarpart]'})
class ToolbarPart {
templateRef: TemplateRef;
constructor(templateRef: TemplateRef) {
this.templateRef = templateRef;
}
}
@Directive({selector: '[toolbarVc]', inputs: ['toolbarVc']})
class ToolbarViewContainer {
constructor(public vc: ViewContainerRef) {}
set toolbarVc(part: ToolbarPart) {
this.vc.createEmbeddedView(part.templateRef, new ToolbarContext('From toolbar'), 0);
}
}
@Component({
selector: 'toolbar',
template: 'TOOLBAR(
)',
})
class ToolbarComponent {
// TODO(issue/24571): remove '!'.
@ContentChildren(ToolbarPart) query!: QueryList;
ctxProp: string = 'hello world';
constructor() {}
}
@Directive({selector: '[two-way]', inputs: ['control'], outputs: ['controlChange']})
class DirectiveWithTwoWayBinding {
controlChange = new EventEmitter();
control: any = null;
triggerChange(value: any) {
this.controlChange.emit(value);
}
}
@Injectable()
class InjectableService {
}
function createInjectableWithLogging(inj: Injector) {
inj.get(ComponentProvidingLoggingInjectable).created = true;
return new InjectableService();
}
@Component({
selector: 'component-providing-logging-injectable',
providers:
[{provide: InjectableService, useFactory: createInjectableWithLogging, deps: [Injector]}],
template: ''
})
class ComponentProvidingLoggingInjectable {
created: boolean = false;
}
@Directive({selector: 'directive-providing-injectable', providers: [[InjectableService]]})
class DirectiveProvidingInjectable {
}
@Component({
selector: 'directive-providing-injectable',
viewProviders: [[InjectableService]],
template: ''
})
class DirectiveProvidingInjectableInView {
}
@Component({
selector: 'directive-providing-injectable',
providers: [{provide: InjectableService, useValue: 'host'}],
viewProviders: [{provide: InjectableService, useValue: 'view'}],
template: ''
})
class DirectiveProvidingInjectableInHostAndView {
}
@Component({selector: 'directive-consuming-injectable', template: ''})
class DirectiveConsumingInjectable {
injectable: any;
constructor(@Host() @Inject(InjectableService) injectable: any) {
this.injectable = injectable;
}
}
@Component({selector: 'directive-containing-directive-consuming-an-injectable'})
class DirectiveContainingDirectiveConsumingAnInjectable {
directive: any;
}
@Component({selector: 'directive-consuming-injectable-unbounded', template: ''})
class DirectiveConsumingInjectableUnbounded {
injectable: any;
constructor(
injectable: InjectableService,
@SkipSelf() parent: DirectiveContainingDirectiveConsumingAnInjectable) {
this.injectable = injectable;
parent.directive = this;
}
}
class EventBus {
parentEventBus: EventBus;
name: string;
constructor(parentEventBus: EventBus, name: string) {
this.parentEventBus = parentEventBus;
this.name = name;
}
}
@Directive({
selector: 'grand-parent-providing-event-bus',
providers: [{provide: EventBus, useValue: new EventBus(null!, 'grandparent')}]
})
class GrandParentProvidingEventBus {
bus: EventBus;
constructor(bus: EventBus) {
this.bus = bus;
}
}
function createParentBus(peb: EventBus) {
return new EventBus(peb, 'parent');
}
@Component({
selector: 'parent-providing-event-bus',
providers: [{provide: EventBus, useFactory: createParentBus, deps: [[EventBus, new SkipSelf()]]}],
template: ` `
})
class ParentProvidingEventBus {
bus: EventBus;
grandParentBus: EventBus;
constructor(bus: EventBus, @SkipSelf() grandParentBus: EventBus) {
this.bus = bus;
this.grandParentBus = grandParentBus;
}
}
@Directive({selector: 'child-consuming-event-bus'})
class ChildConsumingEventBus {
bus: EventBus;
constructor(@SkipSelf() bus: EventBus) {
this.bus = bus;
}
}
@Directive({selector: '[someImpvp]', inputs: ['someImpvp']})
class SomeImperativeViewport {
view: EmbeddedViewRef|null;
anchor: any;
constructor(
public vc: ViewContainerRef, public templateRef: TemplateRef,
@Inject(ANCHOR_ELEMENT) anchor: any) {
this.view = null;
this.anchor = anchor;
}
set someImpvp(value: boolean) {
if (this.view) {
this.vc.clear();
this.view = null;
}
if (value) {
this.view = this.vc.createEmbeddedView(this.templateRef);
const nodes = this.view.rootNodes;
for (let i = 0; i < nodes.length; i++) {
this.anchor.appendChild(nodes[i]);
}
}
}
}
@Directive({selector: '[export-dir]', exportAs: 'dir'})
class ExportDir {
}
@Directive({selector: '[multiple-export-as]', exportAs: 'dirX, dirY'})
export class DirectiveWithMultipleExportAsNames {
}
@Component({selector: 'comp'})
class ComponentWithoutView {
}
@Directive({selector: '[no-duplicate]'})
class DuplicateDir {
constructor(elRef: ElementRef) {
elRef.nativeElement.textContent += 'noduplicate';
}
}
@Directive({selector: '[no-duplicate]'})
class OtherDuplicateDir {
constructor(elRef: ElementRef) {
elRef.nativeElement.textContent += 'othernoduplicate';
}
}
@Directive({selector: 'directive-throwing-error'})
class DirectiveThrowingAnError {
constructor() {
throw new Error('BOOM');
}
}
@Component({
selector: 'component-with-template',
template: `No View Decorator: {{item}}
`
})
class ComponentWithTemplate {
items = [1, 2, 3];
}
@Directive({selector: 'with-prop-decorators'})
class DirectiveWithPropDecorators {
target: any;
// TODO(issue/24571): remove '!'.
@Input('elProp') dirProp!: string;
@Output('elEvent') event = new EventEmitter();
// TODO(issue/24571): remove '!'.
@HostBinding('attr.my-attr') myAttr!: string;
@HostListener('click', ['$event.target'])
onClick(target: any) {
this.target = target;
}
fireEvent(msg: any) {
this.event.emit(msg);
}
}
@Component({selector: 'some-cmp'})
class SomeCmp {
value: any;
}
@Component({
selector: 'parent-cmp',
template: ` `,
})
export class ParentCmp {
name: string = 'hello';
}
@Component({selector: 'cmp', template: ''})
class SomeCmpWithInput {
@Input() test$: any;
}
function isPrevented(evt: Event): boolean {
return evt.defaultPrevented || evt.returnValue != null && !evt.returnValue;
}