Skip to content
Permalink
Browse files

fix(common): reflect input type in NgForOf context (#33997)

Fixes `NgForOf` not reflecting the type of its input in the `NgForOfContext`.

PR Close #33997
  • Loading branch information
crisbeto authored and mhevery committed Nov 28, 2019
1 parent c3c363f commit b640d38fd1b19c20ebe17d33b87ffa4020e930d4
@@ -6,15 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, TemplateRef, TrackByFunction, ViewContainerRef, forwardRef, isDevMode} from '@angular/core';
import {Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, TemplateRef, TrackByFunction, ViewContainerRef, isDevMode} from '@angular/core';

/**
* @publicApi
*/
export class NgForOfContext<T> {
constructor(
public $implicit: T, public ngForOf: NgIterable<T>, public index: number,
public count: number) {}
export class NgForOfContext<T, U extends NgIterable<T>> {
constructor(public $implicit: T, public ngForOf: U, public index: number, public count: number) {}

get first(): boolean { return this.index === 0; }

@@ -123,13 +121,13 @@ export class NgForOfContext<T> {
* @publicApi
*/
@Directive({selector: '[ngFor][ngForOf]'})
export class NgForOf<T> implements DoCheck {
export class NgForOf<T, U extends NgIterable<T>> implements DoCheck {
/**
* The value of the iterable expression, which can be used as a
* [template input variable](guide/structural-directives#template-input-variable).
*/
@Input()
set ngForOf(ngForOf: NgIterable<T>|undefined|null) {
set ngForOf(ngForOf: U&NgIterable<T>|undefined|null) {
this._ngForOf = ngForOf;
this._ngForOfDirty = true;
}
@@ -165,22 +163,22 @@ export class NgForOf<T> implements DoCheck {

get ngForTrackBy(): TrackByFunction<T> { return this._trackByFn; }

private _ngForOf: NgIterable<T>|undefined|null = null;
private _ngForOf: U|undefined|null = null;
private _ngForOfDirty: boolean = true;
private _differ: IterableDiffer<T>|null = null;
// TODO(issue/24571): remove '!'.
private _trackByFn !: TrackByFunction<T>;

constructor(
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForOfContext<T>>,
private _differs: IterableDiffers) {}
private _viewContainer: ViewContainerRef,
private _template: TemplateRef<NgForOfContext<T, U>>, private _differs: IterableDiffers) {}

/**
* A reference to the template that is stamped out for each item in the iterable.
* @see [template reference variable](guide/template-syntax#template-reference-variables--var-)
*/
@Input()
set ngForTemplate(value: TemplateRef<NgForOfContext<T>>) {
set ngForTemplate(value: TemplateRef<NgForOfContext<T, U>>) {
// TODO(TS2.1): make TemplateRef<Partial<NgForRowOf<T>>> once we move to TS v2.1
// The current type is too restrictive; a template that just uses index, for example,
// should be acceptable.
@@ -213,7 +211,7 @@ export class NgForOf<T> implements DoCheck {
}

private _applyChanges(changes: IterableChanges<T>) {
const insertTuples: RecordViewTuple<T>[] = [];
const insertTuples: RecordViewTuple<T, U>[] = [];
changes.forEachOperation(
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number | null,
currentIndex: number | null) => {
@@ -222,17 +220,17 @@ export class NgForOf<T> implements DoCheck {
// that a new item needs to be inserted from the iterable. This implies that
// there is an iterable value for "_ngForOf".
const view = this._viewContainer.createEmbeddedView(
this._template, new NgForOfContext<T>(null !, this._ngForOf !, -1, -1),
this._template, new NgForOfContext<T, U>(null !, this._ngForOf !, -1, -1),
currentIndex === null ? undefined : currentIndex);
const tuple = new RecordViewTuple<T>(item, view);
const tuple = new RecordViewTuple<T, U>(item, view);
insertTuples.push(tuple);
} else if (currentIndex == null) {
this._viewContainer.remove(
adjustedPreviousIndex === null ? undefined : adjustedPreviousIndex);
} else if (adjustedPreviousIndex !== null) {
const view = this._viewContainer.get(adjustedPreviousIndex) !;
this._viewContainer.move(view, currentIndex);
const tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForOfContext<T>>>view);
const tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForOfContext<T, U>>>view);
insertTuples.push(tuple);
}
});
@@ -242,21 +240,21 @@ export class NgForOf<T> implements DoCheck {
}

for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
const viewRef = <EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(i);
const viewRef = <EmbeddedViewRef<NgForOfContext<T, U>>>this._viewContainer.get(i);
viewRef.context.index = i;
viewRef.context.count = ilen;
viewRef.context.ngForOf = this._ngForOf !;
}

changes.forEachIdentityChange((record: any) => {
const viewRef =
<EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(record.currentIndex);
<EmbeddedViewRef<NgForOfContext<T, U>>>this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item;
});
}

private _perViewChange(
view: EmbeddedViewRef<NgForOfContext<T>>, record: IterableChangeRecord<any>) {
view: EmbeddedViewRef<NgForOfContext<T, U>>, record: IterableChangeRecord<any>) {
view.context.$implicit = record.item;
}

@@ -266,13 +264,14 @@ export class NgForOf<T> implements DoCheck {
* The presence of this method is a signal to the Ivy template type-check compiler that the
* `NgForOf` structural directive renders its template with a specific context type.
*/
static ngTemplateContextGuard<T>(dir: NgForOf<T>, ctx: any): ctx is NgForOfContext<T> {
static ngTemplateContextGuard<T, U extends NgIterable<T>>(dir: NgForOf<T, U>, ctx: any):
ctx is NgForOfContext<T, U> {
return true;
}
}

class RecordViewTuple<T> {
constructor(public record: any, public view: EmbeddedViewRef<NgForOfContext<T>>) {}
class RecordViewTuple<T, U extends NgIterable<T>> {
constructor(public record: any, public view: EmbeddedViewRef<NgForOfContext<T, U>>) {}
}

function getTypeName(type: any): string {

0 comments on commit b640d38

Please sign in to comment.
You can’t perform that action at this time.