feat(NgComponentOutlet): add NgComponentOutlet directive
Add NgComponentOutlet directive that can be used to dynamically create host views from a supplied component. Closes #11168 Takes over PR #11235
This commit is contained in:
parent
c0178de0e2
commit
8578682dcf
|
@ -14,7 +14,7 @@
|
||||||
export * from './location/index';
|
export * from './location/index';
|
||||||
export {NgLocaleLocalization, NgLocalization} from './localization';
|
export {NgLocaleLocalization, NgLocalization} from './localization';
|
||||||
export {CommonModule} from './common_module';
|
export {CommonModule} from './common_module';
|
||||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
|
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
|
||||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||||
export {VERSION} from './version';
|
export {VERSION} from './version';
|
||||||
export {Version} from '@angular/core';
|
export {Version} from '@angular/core';
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {Provider} from '@angular/core';
|
import {Provider} from '@angular/core';
|
||||||
|
|
||||||
import {NgClass} from './ng_class';
|
import {NgClass} from './ng_class';
|
||||||
|
import {NgComponentOutlet} from './ng_component_outlet';
|
||||||
import {NgFor} from './ng_for';
|
import {NgFor} from './ng_for';
|
||||||
import {NgIf} from './ng_if';
|
import {NgIf} from './ng_if';
|
||||||
import {NgPlural, NgPluralCase} from './ng_plural';
|
import {NgPlural, NgPluralCase} from './ng_plural';
|
||||||
|
@ -18,6 +19,7 @@ import {NgTemplateOutlet} from './ng_template_outlet';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
NgClass,
|
NgClass,
|
||||||
|
NgComponentOutlet,
|
||||||
NgFor,
|
NgFor,
|
||||||
NgIf,
|
NgIf,
|
||||||
NgPlural,
|
NgPlural,
|
||||||
|
@ -29,12 +31,14 @@ export {
|
||||||
NgTemplateOutlet
|
NgTemplateOutlet
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of Angular directives that are likely to be used in each and every Angular
|
* A collection of Angular directives that are likely to be used in each and every Angular
|
||||||
* application.
|
* application.
|
||||||
*/
|
*/
|
||||||
export const COMMON_DIRECTIVES: Provider[] = [
|
export const COMMON_DIRECTIVES: Provider[] = [
|
||||||
NgClass,
|
NgClass,
|
||||||
|
NgComponentOutlet,
|
||||||
NgFor,
|
NgFor,
|
||||||
NgIf,
|
NgIf,
|
||||||
NgTemplateOutlet,
|
NgTemplateOutlet,
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnChanges, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a single {@link Component} type and inserts its Host View into current View.
|
||||||
|
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
|
||||||
|
*
|
||||||
|
* `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
|
||||||
|
* any existing component will get destroyed.
|
||||||
|
*
|
||||||
|
* ### Fine tune control
|
||||||
|
*
|
||||||
|
* You can control the component creation process by using the following optional attributes:
|
||||||
|
*
|
||||||
|
* * `ngOutletInjector`: Optional custom {@link Injector} that will be used as parent for the
|
||||||
|
* Component.
|
||||||
|
* Defaults to the injector of the current view container.
|
||||||
|
*
|
||||||
|
* * `ngOutletProviders`: Optional injectable objects ({@link Provider}) that are visible to the
|
||||||
|
* component.
|
||||||
|
*
|
||||||
|
* * `ngOutletContent`: Optional list of projectable nodes to insert into the content
|
||||||
|
* section of the component, if exists. ({@link NgContent}).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* ### Syntax
|
||||||
|
*
|
||||||
|
* Simple
|
||||||
|
* ```
|
||||||
|
* <ng-container *ngComponentOutlet="componentTypeExpression"></ng-container>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Customized
|
||||||
|
* ```
|
||||||
|
* <ng-container *ngComponentOutlet="componentTypeExpression;
|
||||||
|
* injector: injectorExpression;
|
||||||
|
* content: contentNodesExpression">
|
||||||
|
* </ng-container>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* # Example
|
||||||
|
*
|
||||||
|
* {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
|
||||||
|
*
|
||||||
|
* A more complete example with additional options:
|
||||||
|
*
|
||||||
|
* {@example common/ngComponentOutlet/ts/module.ts region='CompleteExample'}
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
@Directive({selector: '[ngComponentOutlet]'})
|
||||||
|
export class NgComponentOutlet implements OnChanges {
|
||||||
|
@Input() ngComponentOutlet: Type<any>;
|
||||||
|
@Input() ngComponentOutletInjector: Injector;
|
||||||
|
@Input() ngComponentOutletContent: any[][];
|
||||||
|
|
||||||
|
componentRef: ComponentRef<any>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _cmpFactoryResolver: ComponentFactoryResolver,
|
||||||
|
private _viewContainerRef: ViewContainerRef) {}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (this.componentRef) {
|
||||||
|
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this.componentRef.hostView));
|
||||||
|
}
|
||||||
|
this._viewContainerRef.clear();
|
||||||
|
this.componentRef = null;
|
||||||
|
|
||||||
|
if (this.ngComponentOutlet) {
|
||||||
|
let injector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;
|
||||||
|
|
||||||
|
this.componentRef = this._viewContainerRef.createComponent(
|
||||||
|
this._cmpFactoryResolver.resolveComponentFactory(this.ngComponentOutlet),
|
||||||
|
this._viewContainerRef.length, injector, this.ngComponentOutletContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
|
||||||
|
import {Component, ComponentRef, Inject, Injector, NO_ERRORS_SCHEMA, NgModule, OpaqueToken, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
|
||||||
|
import {TestBed, async} from '@angular/core/testing';
|
||||||
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('insert/remove', () => {
|
||||||
|
|
||||||
|
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
|
||||||
|
|
||||||
|
it('should do nothing if component is null', async(() => {
|
||||||
|
const template = `<template *ngComponentOutlet="currentComponent"></template>`;
|
||||||
|
TestBed.overrideComponent(TestComponent, {set: {template: template}});
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.componentInstance.currentComponent = null;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should insert content specified by a component', async(() => {
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('foo');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should emit a ComponentRef once a component was created', async(() => {
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
|
fixture.componentInstance.cmpRef = null;
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('foo');
|
||||||
|
expect(fixture.componentInstance.cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||||
|
expect(fixture.componentInstance.cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should clear view if component becomes null', async(() => {
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('foo');
|
||||||
|
|
||||||
|
fixture.componentInstance.currentComponent = null;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should swap content if component changes', async(() => {
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('foo');
|
||||||
|
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponentAgain;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('bar');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should use the injector, if one supplied', async(() => {
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
const uniqueValue = {};
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
|
fixture.componentInstance.injector = ReflectiveInjector.resolveAndCreate(
|
||||||
|
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef;
|
||||||
|
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||||
|
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||||
|
expect(cmpRef.instance.testToken).toBe(uniqueValue);
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should resolve a with injector', async(() => {
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.componentInstance.cmpRef = null;
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
|
fixture.detectChanges();
|
||||||
|
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef;
|
||||||
|
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||||
|
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||||
|
expect(cmpRef.instance.testToken).toBeNull();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should render projectable nodes, if supplied', async(() => {
|
||||||
|
const template = `<template>projected foo</template>${TEST_CMP_TEMPLATE}`;
|
||||||
|
TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||||
|
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
||||||
|
|
||||||
|
TestBed
|
||||||
|
.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
|
||||||
|
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
||||||
|
|
||||||
|
let fixture = TestBed.createComponent(TestComponent);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
|
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||||
|
fixture.componentInstance.projectables =
|
||||||
|
[fixture.componentInstance.vcRef
|
||||||
|
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
|
||||||
|
.rootNodes];
|
||||||
|
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('projected foo');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_TOKEN = new OpaqueToken('TestToken');
|
||||||
|
@Component({selector: 'injected-component', template: 'foo'})
|
||||||
|
class InjectedComponent {
|
||||||
|
constructor(@Optional() @Inject(TEST_TOKEN) public testToken: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Component({selector: 'injected-component-again', template: 'bar'})
|
||||||
|
class InjectedComponentAgain {
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_CMP_TEMPLATE =
|
||||||
|
`<template *ngComponentOutlet="currentComponent; injector: injector; content: projectables"></template>`;
|
||||||
|
@Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
|
||||||
|
class TestComponent {
|
||||||
|
currentComponent: Type<any>;
|
||||||
|
injector: Injector;
|
||||||
|
projectables: any[][];
|
||||||
|
|
||||||
|
get cmpRef(): ComponentRef<any> { return this.ngComponentOutlet.componentRef; }
|
||||||
|
set cmpRef(value: ComponentRef<any>) { this.ngComponentOutlet.componentRef = value; }
|
||||||
|
|
||||||
|
@ViewChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
||||||
|
@ViewChild(NgComponentOutlet) ngComponentOutlet: NgComponentOutlet;
|
||||||
|
|
||||||
|
constructor(public vcRef: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [CommonModule],
|
||||||
|
declarations: [TestComponent, InjectedComponent, InjectedComponentAgain],
|
||||||
|
exports: [TestComponent, InjectedComponent, InjectedComponentAgain],
|
||||||
|
entryComponents: [InjectedComponent, InjectedComponentAgain]
|
||||||
|
})
|
||||||
|
export class TestModule {
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {$, ExpectedConditions, browser, by, element} from 'protractor';
|
||||||
|
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
|
||||||
|
|
||||||
|
function waitForElement(selector: string) {
|
||||||
|
const EC = ExpectedConditions;
|
||||||
|
// Waits for the element with id 'abc' to be present on the dom.
|
||||||
|
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ngComponentOutlet', () => {
|
||||||
|
const URL = 'common/ngComponentOutlet/ts/';
|
||||||
|
afterEach(verifyNoBrowserErrors);
|
||||||
|
|
||||||
|
describe('ng-component-outlet-example', () => {
|
||||||
|
it('should render simple', () => {
|
||||||
|
browser.get(URL);
|
||||||
|
waitForElement('ng-component-outlet-simple-example');
|
||||||
|
expect(element.all(by.css('hello-world')).getText()).toEqual(['Hello World!']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render complete', () => {
|
||||||
|
browser.get(URL);
|
||||||
|
waitForElement('ng-component-outlet-complete-example');
|
||||||
|
expect(element.all(by.css('complete-component')).getText()).toEqual(['Complete: Ahoj Svet!']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, Injectable, Injector, NgModule, ReflectiveInjector} from '@angular/core';
|
||||||
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// #docregion SimpleExample
|
||||||
|
@Component({selector: 'hello-world', template: 'Hello World!'})
|
||||||
|
class HelloWorld {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ng-component-outlet-simple-example',
|
||||||
|
template: `<ng-container *ngComponentOutlet="HelloWorld"></ng-container>`
|
||||||
|
})
|
||||||
|
class NgTemplateOutletSimpleExample {
|
||||||
|
// This field is necessary to expose HelloWorld to the template.
|
||||||
|
HelloWorld = HelloWorld;
|
||||||
|
}
|
||||||
|
// #enddocregion
|
||||||
|
|
||||||
|
// #docregion CompleteExample
|
||||||
|
@Injectable()
|
||||||
|
class Greeter {
|
||||||
|
suffix = '!'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'complete-component',
|
||||||
|
template: `Complete: <ng-content></ng-content> <ng-content></ng-content>{{ greeter.suffix }}`
|
||||||
|
})
|
||||||
|
class CompleteComponent {
|
||||||
|
constructor(public greeter: Greeter) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ng-component-outlet-complete-example',
|
||||||
|
template: `
|
||||||
|
<ng-container *ngComponentOutlet="CompleteComponent;
|
||||||
|
injector: myInjector;
|
||||||
|
content: myContent"></ng-container>`
|
||||||
|
})
|
||||||
|
class NgTemplateOutletCompleteExample {
|
||||||
|
// This field is necessary to expose CompleteComponent to the template.
|
||||||
|
CompleteComponent = CompleteComponent;
|
||||||
|
myInjector: Injector;
|
||||||
|
|
||||||
|
myContent = [[document.createTextNode('Ahoj')], [document.createTextNode('Svet')]];
|
||||||
|
|
||||||
|
constructor(injector: Injector) {
|
||||||
|
this.myInjector = ReflectiveInjector.resolveAndCreate([Greeter], injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'example-app',
|
||||||
|
template: `<ng-component-outlet-simple-example></ng-component-outlet-simple-example>
|
||||||
|
<hr/>
|
||||||
|
<ng-component-outlet-complete-example></ng-component-outlet-complete-example>`
|
||||||
|
})
|
||||||
|
class ExampleApp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [BrowserModule],
|
||||||
|
declarations: [
|
||||||
|
ExampleApp, NgTemplateOutletSimpleExample, NgTemplateOutletCompleteExample, HelloWorld,
|
||||||
|
CompleteComponent
|
||||||
|
],
|
||||||
|
entryComponents: [HelloWorld, CompleteComponent],
|
||||||
|
bootstrap: [ExampleApp]
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
}
|
|
@ -117,6 +117,16 @@ export declare class NgClass implements DoCheck {
|
||||||
ngDoCheck(): void;
|
ngDoCheck(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare class NgComponentOutlet implements OnChanges {
|
||||||
|
componentRef: ComponentRef<any>;
|
||||||
|
ngComponentOutlet: Type<any>;
|
||||||
|
ngComponentOutletContent: any[][];
|
||||||
|
ngComponentOutletInjector: Injector;
|
||||||
|
constructor(_cmpFactoryResolver: ComponentFactoryResolver, _viewContainerRef: ViewContainerRef);
|
||||||
|
ngOnChanges(changes: SimpleChanges): void;
|
||||||
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class NgFor implements DoCheck, OnChanges {
|
export declare class NgFor implements DoCheck, OnChanges {
|
||||||
ngForOf: any;
|
ngForOf: any;
|
||||||
|
|
Loading…
Reference in New Issue