Skip to content

Commit

Permalink
fix(common): strict type checking for ngtemplateoutlet (#48374)
Browse files Browse the repository at this point in the history
When we create a context to inject inside our ngTemplateOutlet, the context was declare as Object, therefore, there are no compilation error.

Now if we add a context, we get error at compile type.

BREAKING CHANGE:  If the 'ngTemplateOutletContext' is different from the context, it will result in a compile-time error.

Before the change, the following template was compiling:

```typescript
interface MyContext {
  $implicit: string;
}

@component({
  standalone: true,
  imports: [NgTemplateOutlet],
  selector: 'person',
  template: `
    <ng-container
      *ngTemplateOutlet="
        myTemplateRef;
        context: { $implicit: 'test', xxx: 'xxx' }
      "></ng-container>
  `,
})
export class PersonComponent {
  myTemplateRef!: TemplateRef<MyContext>;
}
```
However, it does not compile now because the 'xxx' property does not exist in 'MyContext', resulting in the error: 'Type '{ $implicit: string; xxx: string; }' is not assignable to type 'MyContext'.'

The solution is either:
- add the 'xxx' property to 'MyContext' with the correct type or
- add '$any(...)' inside the template to make the error disappear. However, adding '$any(...)' does not correct the error but only preserves the previous behavior of the code.

fix #43510

PR Close #48374
  • Loading branch information
tomalaforge authored and atscott committed Mar 23, 2023
1 parent 585e34b commit d47fef7
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 18 deletions.
10 changes: 5 additions & 5 deletions goldens/public-api/common/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,17 +703,17 @@ export class NgSwitchDefault {
}

// @public
export class NgTemplateOutlet implements OnChanges {
export class NgTemplateOutlet<C = unknown> implements OnChanges {
constructor(_viewContainerRef: ViewContainerRef);
// (undocumented)
ngOnChanges(changes: SimpleChanges): void;
ngTemplateOutlet: TemplateRef<any> | null;
ngTemplateOutletContext: Object | null;
ngTemplateOutlet: TemplateRef<C> | null;
ngTemplateOutletContext: C | null;
ngTemplateOutletInjector: Injector | null;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": { "alias": "ngTemplateOutletContext"; "required": false; }; "ngTemplateOutlet": { "alias": "ngTemplateOutlet"; "required": false; }; "ngTemplateOutletInjector": { "alias": "ngTemplateOutletInjector"; "required": false; }; }, {}, never, never, true, never>;
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet<any>, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": { "alias": "ngTemplateOutletContext"; "required": false; }; "ngTemplateOutlet": { "alias": "ngTemplateOutlet"; "required": false; }; "ngTemplateOutletInjector": { "alias": "ngTemplateOutletInjector"; "required": false; }; }, {}, never, never, true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<NgTemplateOutlet, never>;
static ɵfac: i0.ɵɵFactoryDeclaration<NgTemplateOutlet<any>, never>;
}

// @public
Expand Down
15 changes: 8 additions & 7 deletions packages/common/src/directives/ng_template_outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,21 @@ import {Directive, EmbeddedViewRef, Injector, Input, OnChanges, SimpleChanges, T
selector: '[ngTemplateOutlet]',
standalone: true,
})
export class NgTemplateOutlet implements OnChanges {
private _viewRef: EmbeddedViewRef<any>|null = null;
export class NgTemplateOutlet<C = unknown> implements OnChanges {
private _viewRef: EmbeddedViewRef<C>|null = null;

/**
* A context object to attach to the {@link EmbeddedViewRef}. This should be an
* object, the object's keys will be available for binding by the local template `let`
* declarations.
* Using the key `$implicit` in the context object will set its value as default.
*/
@Input() public ngTemplateOutletContext: Object|null = null;
@Input() public ngTemplateOutletContext: C|null = null;

/**
* A string defining the template reference and optionally the context object for the template.
*/
@Input() public ngTemplateOutlet: TemplateRef<any>|null = null;
@Input() public ngTemplateOutlet: TemplateRef<C>|null = null;

/** Injector to be used within the embedded view. */
@Input() public ngTemplateOutletInjector: Injector|null = null;
Expand All @@ -70,10 +70,11 @@ export class NgTemplateOutlet implements OnChanges {
const {
ngTemplateOutlet: template,
ngTemplateOutletContext: context,
ngTemplateOutletInjector: injector
ngTemplateOutletInjector: injector,
} = this;
this._viewRef = viewContainerRef.createEmbeddedView(
template, context, injector ? {injector} : undefined);
this._viewRef =
viewContainerRef.createEmbeddedView(
template, context, injector ? {injector} : undefined) as EmbeddedViewRef<C>;
} else {
this._viewRef = null;
}
Expand Down
11 changes: 5 additions & 6 deletions packages/compiler-cli/src/ngtsc/testing/fake_common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,16 @@ export declare class NgIf<T = unknown> {
ctx is NgIfContext<Exclude<T, false|0|''|null|undefined>>;
}

export declare class NgTemplateOutlet {
ngTemplateOutlet: TemplateRef<any>|null;
ngTemplateOutletContext: Object|null;
export declare class NgTemplateOutlet<C = unknown> {
ngTemplateOutlet: TemplateRef<C>|null;
ngTemplateOutletContext: C|null;

static ɵdir: ɵɵDirectiveDeclaration < NgTemplateOutlet, '[ngTemplateOutlet]', never, {
static ɵdir: ɵɵDirectiveDeclaration < NgTemplateOutlet<any>, '[ngTemplateOutlet]', never, {
'ngTemplateOutlet': 'ngTemplateOutlet';
'ngTemplateOutletContext': 'ngTemplateOutletContext';
}
, {}, never > ;
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any):
ctx is NgIfContext<Exclude<T, false|0|''|null|undefined>>;
static ngTemplateContextGuard<T>(dir: NgTemplateOutlet<T>, ctx: any): ctx is T;
}

export declare class DatePipe {
Expand Down

0 comments on commit d47fef7

Please sign in to comment.