Previously, injector definitions contained a `factory` property that
was used to create a new instance of the associated NgModule class.
Now this factory has been moved to its own `ɵfac` static property on the
NgModule class itself. This is inline with how directives, components and
pipes are created.
There is a small size increase to bundle sizes for each NgModule class,
because the `ɵfac` takes up a bit more space:
Before:
```js
let a = (() => {
  class n {}
  return n.\u0275mod = c.Cb({type: n}),
  n.\u0275inj = c.Bb({factory: function(t) { return new (t || n) }, imports: [[e.a.forChild(s)], e.a]}),
  n
})(),
```
After:
```js
let a = (() => {
  class n {}
  return n.\u0275fac = function(t) { return new (t || n) },
  n.\u0275mod = c.Cb({type: n}),
  n.\u0275inj = c.Bb({imports: [[r.a.forChild(s)], r.a]}),
  n
})(),
```
In other words `n.\u0275fac = ` is longer than `factory: ` (by 5 characters)
and only because the tooling insists on encoding `ɵ` as `\u0275`.
This can be mitigated in a future PR by only generating the `ɵfac` property
if it is actually needed.
PR Close #41022
		
	
			
		
			
				
	
	
		
			221 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			221 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC 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 {ViewEncapsulation, ɵɵdefineInjectable, ɵɵdefineInjector} from '../../src/core';
 | |
| import {createInjector} from '../../src/di/r3_injector';
 | |
| import {AttributeMarker, markDirty, ɵɵadvance, ɵɵdefineComponent, ɵɵdirectiveInject, ɵɵproperty, ɵɵtemplate} from '../../src/render3/index';
 | |
| import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtext, ɵɵtextInterpolate} from '../../src/render3/instructions/all';
 | |
| import {RenderFlags} from '../../src/render3/interfaces/definition';
 | |
| 
 | |
| import {NgIf} from './common_with_def';
 | |
| import {ComponentFixture, containerEl, createComponent, MockRendererFactory, renderComponent, requestAnimationFrame, toHtml} from './render_util';
 | |
| 
 | |
| describe('component', () => {
 | |
|   class CounterComponent {
 | |
|     count = 0;
 | |
| 
 | |
|     increment() {
 | |
|       this.count++;
 | |
|     }
 | |
| 
 | |
|     static ɵfac = () => new CounterComponent;
 | |
|     static ɵcmp = ɵɵdefineComponent({
 | |
|       type: CounterComponent,
 | |
|       encapsulation: ViewEncapsulation.None,
 | |
|       selectors: [['counter']],
 | |
|       decls: 1,
 | |
|       vars: 1,
 | |
|       template:
 | |
|           function(rf: RenderFlags, ctx: CounterComponent) {
 | |
|             if (rf & RenderFlags.Create) {
 | |
|               ɵɵtext(0);
 | |
|             }
 | |
|             if (rf & RenderFlags.Update) {
 | |
|               ɵɵtextInterpolate(ctx.count);
 | |
|             }
 | |
|           },
 | |
|       inputs: {count: 'count'},
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   describe('renderComponent', () => {
 | |
|     it('should render on initial call', () => {
 | |
|       renderComponent(CounterComponent);
 | |
|       expect(toHtml(containerEl)).toEqual('0');
 | |
|     });
 | |
| 
 | |
|     it('should re-render on input change or method invocation', () => {
 | |
|       const component = renderComponent(CounterComponent);
 | |
|       expect(toHtml(containerEl)).toEqual('0');
 | |
|       component.count = 123;
 | |
|       markDirty(component);
 | |
|       expect(toHtml(containerEl)).toEqual('0');
 | |
|       requestAnimationFrame.flush();
 | |
|       expect(toHtml(containerEl)).toEqual('123');
 | |
|       component.increment();
 | |
|       markDirty(component);
 | |
|       expect(toHtml(containerEl)).toEqual('123');
 | |
|       requestAnimationFrame.flush();
 | |
|       expect(toHtml(containerEl)).toEqual('124');
 | |
|     });
 | |
| 
 | |
|     class MyService {
 | |
|       constructor(public value: string) {}
 | |
|       static ɵprov = ɵɵdefineInjectable({
 | |
|         token: MyService,
 | |
|         providedIn: 'root',
 | |
|         factory: () => new MyService('no-injector'),
 | |
|       });
 | |
|     }
 | |
|     class MyComponent {
 | |
|       constructor(public myService: MyService) {}
 | |
|       static ɵfac = () => new MyComponent(ɵɵdirectiveInject(MyService));
 | |
|       static ɵcmp = ɵɵdefineComponent({
 | |
|         type: MyComponent,
 | |
|         encapsulation: ViewEncapsulation.None,
 | |
|         selectors: [['my-component']],
 | |
|         decls: 1,
 | |
|         vars: 1,
 | |
|         template:
 | |
|             function(fs: RenderFlags, ctx: MyComponent) {
 | |
|               if (fs & RenderFlags.Create) {
 | |
|                 ɵɵtext(0);
 | |
|               }
 | |
|               if (fs & RenderFlags.Update) {
 | |
|                 ɵɵtextInterpolate(ctx.myService.value);
 | |
|               }
 | |
|             }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     class MyModule {
 | |
|       static ɵinj = ɵɵdefineInjector(
 | |
|           {providers: [{provide: MyService, useValue: new MyService('injector')}]});
 | |
|     }
 | |
| 
 | |
|     it('should support bootstrapping without injector', () => {
 | |
|       const fixture = new ComponentFixture(MyComponent);
 | |
|       expect(fixture.html).toEqual('no-injector');
 | |
|     });
 | |
| 
 | |
|     it('should support bootstrapping with injector', () => {
 | |
|       const fixture = new ComponentFixture(MyComponent, {injector: createInjector(MyModule)});
 | |
|       expect(fixture.html).toEqual('injector');
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should instantiate components at high indices', () => {
 | |
|     // {{ name }}
 | |
|     class Comp {
 | |
|       // @Input
 | |
|       name = '';
 | |
| 
 | |
|       static ɵfac = () => new Comp();
 | |
|       static ɵcmp = ɵɵdefineComponent({
 | |
|         type: Comp,
 | |
|         selectors: [['comp']],
 | |
|         decls: 1,
 | |
|         vars: 1,
 | |
|         template:
 | |
|             (rf: RenderFlags, ctx: Comp) => {
 | |
|               if (rf & RenderFlags.Create) {
 | |
|                 ɵɵtext(0);
 | |
|               }
 | |
|               if (rf & RenderFlags.Update) {
 | |
|                 ɵɵtextInterpolate(ctx.name);
 | |
|               }
 | |
|             },
 | |
|         inputs: {name: 'name'}
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     // Artificially inflating the slot IDs of this app component to mimic an app
 | |
|     // with a very large view
 | |
|     const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
 | |
|       if (rf & RenderFlags.Create) {
 | |
|         ɵɵelement(4097, 'comp');
 | |
|       }
 | |
|       if (rf & RenderFlags.Update) {
 | |
|         ɵɵadvance(4097);
 | |
|         ɵɵproperty('name', ctx.name);
 | |
|       }
 | |
|     }, 4098, 1, [Comp]);
 | |
| 
 | |
|     const fixture = new ComponentFixture(App);
 | |
|     expect(fixture.html).toEqual('<comp></comp>');
 | |
| 
 | |
|     fixture.component.name = 'some name';
 | |
|     fixture.update();
 | |
|     expect(fixture.html).toEqual('<comp>some name</comp>');
 | |
|   });
 | |
| });
 | |
| 
 | |
| it('should not invoke renderer destroy method for embedded views', () => {
 | |
|   let comp: Comp;
 | |
| 
 | |
|   function MyComponent_div_Template_2(rf: any, ctx: any) {
 | |
|     if (rf & RenderFlags.Create) {
 | |
|       ɵɵelementStart(0, 'div');
 | |
|       ɵɵtext(1, 'Child view');
 | |
|       ɵɵelementEnd();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   class Comp {
 | |
|     visible = true;
 | |
| 
 | |
|     static ɵfac =
 | |
|         () => {
 | |
|           comp = new Comp();
 | |
|           return comp;
 | |
|         }
 | |
| 
 | |
|     static ɵcmp = ɵɵdefineComponent({
 | |
|       type: Comp,
 | |
|       selectors: [['comp']],
 | |
|       decls: 3,
 | |
|       vars: 1,
 | |
|       directives: [NgIf],
 | |
|       consts: [[AttributeMarker.Template, 'ngIf']],
 | |
|       /**
 | |
|        *  <div>Root view</div>
 | |
|        *  <div *ngIf="visible">Child view</div>
 | |
|        */
 | |
|       template:
 | |
|           function(rf: RenderFlags, ctx: Comp) {
 | |
|             if (rf & RenderFlags.Create) {
 | |
|               ɵɵelementStart(0, 'div');
 | |
|               ɵɵtext(1, 'Root view');
 | |
|               ɵɵelementEnd();
 | |
|               ɵɵtemplate(2, MyComponent_div_Template_2, 2, 0, 'div', 0);
 | |
|             }
 | |
|             if (rf & RenderFlags.Update) {
 | |
|               ɵɵadvance(2);
 | |
|               ɵɵproperty('ngIf', ctx.visible);
 | |
|             }
 | |
|           }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   const rendererFactory = new MockRendererFactory(['destroy']);
 | |
|   const fixture = new ComponentFixture(Comp, {rendererFactory});
 | |
| 
 | |
|   comp!.visible = false;
 | |
|   fixture.update();
 | |
| 
 | |
|   comp!.visible = true;
 | |
|   fixture.update();
 | |
| 
 | |
|   const renderer = rendererFactory.lastRenderer!;
 | |
|   const destroySpy = renderer.spies['destroy'];
 | |
| 
 | |
|   // we should never see `destroy` method being called
 | |
|   // in case child views are created/removed
 | |
|   expect(destroySpy.calls.count()).toBe(0);
 | |
| });
 |