feat(upgrade): Support ng-model in downgraded components (#10578)
This commit is contained in:
parent
d3a3a8e1fc
commit
e21e9c5fb7
|
@ -161,6 +161,37 @@ export interface ITestabilityService {
|
||||||
whenStable(callback: Function): void;
|
whenStable(callback: Function): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INgModelController {
|
||||||
|
$render(): void;
|
||||||
|
$isEmpty(value: any): boolean;
|
||||||
|
$setValidity(validationErrorKey: string, isValid: boolean): void;
|
||||||
|
$setPristine(): void;
|
||||||
|
$setDirty(): void;
|
||||||
|
$setUntouched(): void;
|
||||||
|
$setTouched(): void;
|
||||||
|
$rollbackViewValue(): void;
|
||||||
|
$validate(): void;
|
||||||
|
$commitViewValue(): void;
|
||||||
|
$setViewValue(value: any, trigger: string): void;
|
||||||
|
|
||||||
|
$viewValue: any;
|
||||||
|
$modelValue: any;
|
||||||
|
$parsers: Function[];
|
||||||
|
$formatters: Function[];
|
||||||
|
$validators: {[key: string]: Function};
|
||||||
|
$asyncValidators: {[key: string]: Function};
|
||||||
|
$viewChangeListeners: Function[];
|
||||||
|
$error: Object;
|
||||||
|
$pending: Object;
|
||||||
|
$untouched: boolean;
|
||||||
|
$touched: boolean;
|
||||||
|
$pristine: boolean;
|
||||||
|
$dirty: boolean;
|
||||||
|
$valid: boolean;
|
||||||
|
$invalid: boolean;
|
||||||
|
$name: string;
|
||||||
|
}
|
||||||
|
|
||||||
function noNg() {
|
function noNg() {
|
||||||
throw new Error('AngularJS v1.x is not loaded!');
|
throw new Error('AngularJS v1.x is not loaded!');
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
export const UPGRADE_MODULE_NAME = '$$UpgradeModule';
|
export const UPGRADE_MODULE_NAME = '$$UpgradeModule';
|
||||||
export const INJECTOR_KEY = '$$angularInjector';
|
export const INJECTOR_KEY = '$$angularInjector';
|
||||||
|
export const REQUIRE_NG1_MODEL = '?ngModel';
|
||||||
|
|
||||||
export const $INJECTOR = '$injector';
|
export const $INJECTOR = '$injector';
|
||||||
export const $PARSE = '$parse';
|
export const $PARSE = '$parse';
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angul
|
||||||
|
|
||||||
import * as angular from '../angular_js';
|
import * as angular from '../angular_js';
|
||||||
|
|
||||||
import {$INJECTOR, $PARSE, INJECTOR_KEY} from './constants';
|
import {$INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_NG1_MODEL} from './constants';
|
||||||
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
||||||
|
|
||||||
let downgradeCount = 0;
|
let downgradeCount = 0;
|
||||||
|
@ -77,14 +77,16 @@ export function downgradeComponent(info: /* ComponentInfo */ {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
require: '?^' + INJECTOR_KEY,
|
require: ['?^' + INJECTOR_KEY, REQUIRE_NG1_MODEL],
|
||||||
link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
||||||
parentInjector: Injector, transclude: angular.ITranscludeFunction) => {
|
required: any[], transclude: angular.ITranscludeFunction) => {
|
||||||
|
|
||||||
|
let parentInjector: Injector = required[0];
|
||||||
if (parentInjector === null) {
|
if (parentInjector === null) {
|
||||||
parentInjector = $injector.get(INJECTOR_KEY);
|
parentInjector = $injector.get(INJECTOR_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ngModel: angular.INgModelController = required[1];
|
||||||
const componentFactoryResolver: ComponentFactoryResolver =
|
const componentFactoryResolver: ComponentFactoryResolver =
|
||||||
parentInjector.get(ComponentFactoryResolver);
|
parentInjector.get(ComponentFactoryResolver);
|
||||||
const componentFactory: ComponentFactory<any> =
|
const componentFactory: ComponentFactory<any> =
|
||||||
|
@ -95,7 +97,7 @@ export function downgradeComponent(info: /* ComponentInfo */ {
|
||||||
}
|
}
|
||||||
|
|
||||||
const facade = new DowngradeComponentAdapter(
|
const facade = new DowngradeComponentAdapter(
|
||||||
idPrefix + (idCount++), info, element, attrs, scope, parentInjector, $parse,
|
idPrefix + (idCount++), info, element, attrs, scope, ngModel, parentInjector, $parse,
|
||||||
componentFactory);
|
componentFactory);
|
||||||
facade.setupInputs();
|
facade.setupInputs();
|
||||||
facade.createComponent();
|
facade.createComponent();
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
||||||
|
|
||||||
import * as angular from '../angular_js';
|
import * as angular from '../angular_js';
|
||||||
|
import {hookupNgModel} from '../util';
|
||||||
|
|
||||||
import {ComponentInfo, PropertyBinding} from './component_info';
|
import {ComponentInfo, PropertyBinding} from './component_info';
|
||||||
import {$SCOPE} from './constants';
|
import {$SCOPE} from './constants';
|
||||||
|
@ -31,8 +32,8 @@ export class DowngradeComponentAdapter {
|
||||||
constructor(
|
constructor(
|
||||||
private id: string, private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
private id: string, private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
||||||
private attrs: angular.IAttributes, private scope: angular.IScope,
|
private attrs: angular.IAttributes, private scope: angular.IScope,
|
||||||
private parentInjector: Injector, private parse: angular.IParseService,
|
private ngModel: angular.INgModelController, private parentInjector: Injector,
|
||||||
private componentFactory: ComponentFactory<any>) {
|
private parse: angular.IParseService, private componentFactory: ComponentFactory<any>) {
|
||||||
(<any>this.element[0]).id = id;
|
(<any>this.element[0]).id = id;
|
||||||
this.componentScope = scope.$new();
|
this.componentScope = scope.$new();
|
||||||
this.childNodes = <Node[]><any>element.contents();
|
this.childNodes = <Node[]><any>element.contents();
|
||||||
|
@ -47,6 +48,8 @@ export class DowngradeComponentAdapter {
|
||||||
childInjector, [[this.contentInsertionPoint]], this.element[0]);
|
childInjector, [[this.contentInsertionPoint]], this.element[0]);
|
||||||
this.changeDetector = this.componentRef.changeDetectorRef;
|
this.changeDetector = this.componentRef.changeDetectorRef;
|
||||||
this.component = this.componentRef.instance;
|
this.component = this.componentRef.instance;
|
||||||
|
|
||||||
|
hookupNgModel(this.ngModel, this.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupInputs(): void {
|
setupInputs(): void {
|
||||||
|
|
|
@ -22,3 +22,4 @@ export const NG1_PARSE = '$parse';
|
||||||
export const NG1_TEMPLATE_CACHE = '$templateCache';
|
export const NG1_TEMPLATE_CACHE = '$templateCache';
|
||||||
export const NG1_TESTABILITY = '$$testability';
|
export const NG1_TESTABILITY = '$$testability';
|
||||||
export const REQUIRE_INJECTOR = '?^^' + NG2_INJECTOR;
|
export const REQUIRE_INJECTOR = '?^^' + NG2_INJECTOR;
|
||||||
|
export const REQUIRE_NG1_MODEL = '?ngModel';
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injecto
|
||||||
import * as angular from './angular_js';
|
import * as angular from './angular_js';
|
||||||
import {NG1_SCOPE} from './constants';
|
import {NG1_SCOPE} from './constants';
|
||||||
import {ComponentInfo} from './metadata';
|
import {ComponentInfo} from './metadata';
|
||||||
|
import {hookupNgModel} from './util';
|
||||||
|
|
||||||
const INITIAL_VALUE = {
|
const INITIAL_VALUE = {
|
||||||
__UNINITIALIZED__: true
|
__UNINITIALIZED__: true
|
||||||
|
@ -27,8 +28,8 @@ export class DowngradeNg2ComponentAdapter {
|
||||||
constructor(
|
constructor(
|
||||||
private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
||||||
private attrs: angular.IAttributes, private scope: angular.IScope,
|
private attrs: angular.IAttributes, private scope: angular.IScope,
|
||||||
private parentInjector: Injector, private parse: angular.IParseService,
|
private ngModel: angular.INgModelController, private parentInjector: Injector,
|
||||||
private componentFactory: ComponentFactory<any>) {
|
private parse: angular.IParseService, private componentFactory: ComponentFactory<any>) {
|
||||||
this.componentScope = scope.$new();
|
this.componentScope = scope.$new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +41,8 @@ export class DowngradeNg2ComponentAdapter {
|
||||||
this.componentFactory.create(childInjector, projectableNodes, this.element[0]);
|
this.componentFactory.create(childInjector, projectableNodes, this.element[0]);
|
||||||
this.changeDetector = this.componentRef.changeDetectorRef;
|
this.changeDetector = this.componentRef.changeDetectorRef;
|
||||||
this.component = this.componentRef.instance;
|
this.component = this.componentRef.instance;
|
||||||
|
|
||||||
|
hookupNgModel(this.ngModel, this.component);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupInputs(): void {
|
setupInputs(): void {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {Compiler, CompilerOptions, ComponentFactory, Injector, NgModule, NgModul
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
import * as angular from './angular_js';
|
import * as angular from './angular_js';
|
||||||
import {NG1_COMPILE, NG1_INJECTOR, NG1_PARSE, NG1_ROOT_SCOPE, NG1_TESTABILITY, NG2_COMPILER, NG2_COMPONENT_FACTORY_REF_MAP, NG2_INJECTOR, NG2_ZONE, REQUIRE_INJECTOR} from './constants';
|
import {NG1_COMPILE, NG1_INJECTOR, NG1_PARSE, NG1_ROOT_SCOPE, NG1_TESTABILITY, NG2_COMPILER, NG2_COMPONENT_FACTORY_REF_MAP, NG2_INJECTOR, NG2_ZONE, REQUIRE_INJECTOR, REQUIRE_NG1_MODEL} from './constants';
|
||||||
import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter';
|
import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter';
|
||||||
import {ComponentInfo, getComponentInfo} from './metadata';
|
import {ComponentInfo, getComponentInfo} from './metadata';
|
||||||
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
|
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
|
||||||
|
@ -138,6 +138,9 @@ export class UpgradeAdapter {
|
||||||
* 2. Even thought the component is instantiated in Angular 1, it will be using Angular 2+
|
* 2. Even thought the component is instantiated in Angular 1, it will be using Angular 2+
|
||||||
* syntax. This has to be done, this way because we must follow Angular 2+ components do not
|
* syntax. This has to be done, this way because we must follow Angular 2+ components do not
|
||||||
* declare how the attributes should be interpreted.
|
* declare how the attributes should be interpreted.
|
||||||
|
* 3. ng-model is controlled by AngularJS v1 and communicates with the downgraded Ng2 component
|
||||||
|
* by way of the ControlValueAccessor interface from @angular/forms. Only components that
|
||||||
|
* implement this interface are eligible.
|
||||||
*
|
*
|
||||||
* ## Supported Features
|
* ## Supported Features
|
||||||
*
|
*
|
||||||
|
@ -146,6 +149,7 @@ export class UpgradeAdapter {
|
||||||
* - Interpolation: `<comp greeting="Hello {{name}}!">`
|
* - Interpolation: `<comp greeting="Hello {{name}}!">`
|
||||||
* - Expression: `<comp [name]="username">`
|
* - Expression: `<comp [name]="username">`
|
||||||
* - Event: `<comp (close)="doSomething()">`
|
* - Event: `<comp (close)="doSomething()">`
|
||||||
|
* - ng-model: `<comp ng-model="name">`
|
||||||
* - Content projection: yes
|
* - Content projection: yes
|
||||||
*
|
*
|
||||||
* ### Example
|
* ### Example
|
||||||
|
@ -655,18 +659,20 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
terminal: true,
|
terminal: true,
|
||||||
require: REQUIRE_INJECTOR,
|
require: [REQUIRE_INJECTOR, REQUIRE_NG1_MODEL],
|
||||||
compile: (templateElement: angular.IAugmentedJQuery, templateAttributes: angular.IAttributes,
|
compile: (templateElement: angular.IAugmentedJQuery, templateAttributes: angular.IAttributes,
|
||||||
transclude: angular.ITranscludeFunction) => {
|
transclude: angular.ITranscludeFunction) => {
|
||||||
// We might have compile the contents lazily, because this might have been triggered by the
|
// We might have compile the contents lazily, because this might have been triggered by the
|
||||||
// UpgradeNg1ComponentAdapterBuilder, when the ng2 templates have not been compiled yet
|
// UpgradeNg1ComponentAdapterBuilder, when the ng2 templates have not been compiled yet
|
||||||
return {
|
return {
|
||||||
post: (scope: angular.IScope, element: angular.IAugmentedJQuery,
|
post: (scope: angular.IScope, element: angular.IAugmentedJQuery,
|
||||||
attrs: angular.IAttributes, parentInjector: Injector | ParentInjectorPromise,
|
attrs: angular.IAttributes, required: any[],
|
||||||
transclude: angular.ITranscludeFunction): void => {
|
transclude: angular.ITranscludeFunction): void => {
|
||||||
let id = idPrefix + (idCount++);
|
let id = idPrefix + (idCount++);
|
||||||
(<any>element[0]).id = id;
|
(<any>element[0]).id = id;
|
||||||
|
|
||||||
|
let parentInjector: Injector|ParentInjectorPromise = required[0];
|
||||||
|
const ngModel: angular.INgModelController = required[1];
|
||||||
let injectorPromise = new ParentInjectorPromise(element);
|
let injectorPromise = new ParentInjectorPromise(element);
|
||||||
|
|
||||||
const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler;
|
const ng2Compiler = ng1Injector.get(NG2_COMPILER) as Compiler;
|
||||||
|
@ -697,7 +703,7 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
||||||
|
|
||||||
function downgrade(injector: Injector) {
|
function downgrade(injector: Injector) {
|
||||||
const facade = new DowngradeNg2ComponentAdapter(
|
const facade = new DowngradeNg2ComponentAdapter(
|
||||||
info, element, attrs, scope, injector, parse, componentFactory);
|
info, element, attrs, scope, ngModel, injector, parse, componentFactory);
|
||||||
facade.setupInputs();
|
facade.setupInputs();
|
||||||
facade.bootstrapNg2(projectableNodes);
|
facade.bootstrapNg2(projectableNodes);
|
||||||
facade.setupOutputs();
|
facade.setupOutputs();
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as angular from './angular_js';
|
||||||
|
|
||||||
export function onError(e: any) {
|
export function onError(e: any) {
|
||||||
// TODO: (misko): We seem to not have a stack trace here!
|
// TODO: (misko): We seem to not have a stack trace here!
|
||||||
if (console.error) {
|
if (console.error) {
|
||||||
|
@ -46,3 +48,23 @@ export class Deferred<R> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the passed-in component implements the subset of
|
||||||
|
* ControlValueAccessor needed for AngularJS ng-model compatibility.
|
||||||
|
*/
|
||||||
|
function supportsNgModel(component: any) {
|
||||||
|
return typeof component.writeValue === 'function' &&
|
||||||
|
typeof component.registerOnChange === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glue the AngularJS ngModelController if it exists to the component if it
|
||||||
|
* implements the needed subset of ControlValueAccessor.
|
||||||
|
*/
|
||||||
|
export function hookupNgModel(ngModel: angular.INgModelController, component: any) {
|
||||||
|
if (ngModel && supportsNgModel(component)) {
|
||||||
|
ngModel.$render = () => { component.writeValue(ngModel.$viewValue); };
|
||||||
|
component.registerOnChange(ngModel.$setViewValue.bind(ngModel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -142,6 +142,51 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should bind to ng-model', async(() => {
|
||||||
|
const ng1Module = angular.module('ng1', []).run(
|
||||||
|
($rootScope: angular.IScope) => { $rootScope['modelA'] = 'A'; });
|
||||||
|
|
||||||
|
let ng2Instance: Ng2;
|
||||||
|
@Component({selector: 'ng2', template: '<span>{{_value}}</span>'})
|
||||||
|
class Ng2 {
|
||||||
|
private _value: any = '';
|
||||||
|
private _onChangeCallback: (_: any) => void = () => {};
|
||||||
|
constructor() { ng2Instance = this; }
|
||||||
|
writeValue(value: any) { this._value = value; }
|
||||||
|
registerOnChange(fn: any) { this._onChangeCallback = fn; }
|
||||||
|
doChange(newValue: string) {
|
||||||
|
this._value = newValue;
|
||||||
|
this._onChangeCallback(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ng1Module.directive('ng2', downgradeComponent({component: Ng2}));
|
||||||
|
|
||||||
|
const element = html(`<div><ng2 ng-model="modelA"></ng2> | {{modelA}}</div>`);
|
||||||
|
|
||||||
|
@NgModule(
|
||||||
|
{declarations: [Ng2], entryComponents: [Ng2], imports: [BrowserModule, UpgradeModule]})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(Ng2Module).then((ref) => {
|
||||||
|
const adapter = ref.injector.get(UpgradeModule) as UpgradeModule;
|
||||||
|
adapter.bootstrap(element, [ng1Module.name]);
|
||||||
|
const $rootScope = adapter.$injector.get('$rootScope');
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('A | A');
|
||||||
|
|
||||||
|
$rootScope.modelA = 'B';
|
||||||
|
$rootScope.$apply();
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('B | B');
|
||||||
|
|
||||||
|
ng2Instance.doChange('C');
|
||||||
|
expect($rootScope.modelA).toBe('C');
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('C | C');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
|
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
|
||||||
|
|
||||||
let destroyed = false;
|
let destroyed = false;
|
||||||
|
|
|
@ -380,6 +380,52 @@ export function main() {
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should bind to ng-model', async(() => {
|
||||||
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
|
const ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
ng1Module.run(($rootScope: any /** TODO #9100 */) => { $rootScope.modelA = 'A'; });
|
||||||
|
|
||||||
|
let ng2Instance: Ng2;
|
||||||
|
@Component({selector: 'ng2', template: '{{_value}}'})
|
||||||
|
class Ng2 {
|
||||||
|
private _value: any = '';
|
||||||
|
private _onChangeCallback: (_: any) => void = () => {};
|
||||||
|
constructor() { ng2Instance = this; }
|
||||||
|
writeValue(value: any) { this._value = value; }
|
||||||
|
registerOnChange(fn: any) { this._onChangeCallback = fn; }
|
||||||
|
doChange(newValue: string) {
|
||||||
|
this._value = newValue;
|
||||||
|
this._onChangeCallback(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
const element = html(`<div><ng2 ng-model="modelA"></ng2> | {{modelA}}</div>`);
|
||||||
|
|
||||||
|
const Ng2Module = NgModule({
|
||||||
|
declarations: [Ng2],
|
||||||
|
imports: [BrowserModule],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
|
||||||
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
|
let $rootScope: any = ref.ng1RootScope;
|
||||||
|
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('A | A');
|
||||||
|
|
||||||
|
$rootScope.modelA = 'B';
|
||||||
|
$rootScope.$apply();
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('B | B');
|
||||||
|
|
||||||
|
ng2Instance.doChange('C');
|
||||||
|
expect($rootScope.modelA).toBe('C');
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('C | C');
|
||||||
|
|
||||||
|
ref.dispose();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
|
it('should properly run cleanup when ng1 directive is destroyed', async(() => {
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
const ng1Module = angular.module('ng1', []);
|
const ng1Module = angular.module('ng1', []);
|
||||||
|
|
Loading…
Reference in New Issue