Skip to content

Commit

Permalink
Merge pull request #7 from ng-turkey/fix/router-outlet-change-detection
Browse files Browse the repository at this point in the history
Fix/router outlet change detection
  • Loading branch information
mehmet-erim authored Apr 15, 2019
2 parents acf89ea + 6557f2b commit c82cb79
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 33 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-context",
"version": "1.1.0",
"version": "1.1.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
6 changes: 4 additions & 2 deletions src/lib/consumer.abstract.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ChangeDetectorRef, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { parseKeys } from './internals';
import { ContextProviderComponent } from './provider.component';
import { ContextMap } from './symbols';

export abstract class AbstractContextConsumer<T> implements OnChanges, OnDestroy, OnInit {
protected consumed = new Map();
protected destroy$ = new Subject<void>();
protected initialized: boolean;
protected _contextMap = {};
protected _consume: string | string[] = '';

consumed = new Map();

@Input()
set contextMap(map: ContextMap) {
this._contextMap = map || {};
Expand Down Expand Up @@ -48,6 +49,7 @@ export abstract class AbstractContextConsumer<T> implements OnChanges, OnDestroy
this.provider.change$
.pipe(
takeUntil(this.destroy$),
startWith(...Array.from(this.provider.provided.keys())),
filter(key => !!key),
)
.subscribe(providerKey => this.syncProperties(consumed, providerKey));
Expand Down
15 changes: 9 additions & 6 deletions src/lib/disposer.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ import {
Directive,
EmbeddedViewRef,
Input,
OnChanges,
OnDestroy,
Optional,
SkipSelf,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { parseKeys } from './internals';
import { ContextProviderComponent } from './provider.component';

export class Context {
$implicit: { [key: string]: any } = {};
}

@Directive({
selector: '[contextDisposer]',
})
export class ContextDisposerDirective {
export class ContextDisposerDirective implements OnChanges, OnDestroy {
private destroy$ = new Subject<void>();
private _dispose: string | string[] = '';
private view: EmbeddedViewRef<any>;
Expand Down Expand Up @@ -49,6 +55,7 @@ export class ContextDisposerDirective {
this.provider.change$
.pipe(
takeUntil(this.destroy$),
startWith(...Array.from(this.provider.provided.keys())),
filter(key => !!key),
)
.subscribe(providerKey => this.syncProperties(disposed, providerKey));
Expand Down Expand Up @@ -83,7 +90,3 @@ export class ContextDisposerDirective {
if (this.view) this.vcRef.clear();
}
}

export class Context {
$implicit: { [key: string]: any } = {};
}
3 changes: 2 additions & 1 deletion src/lib/provider.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import { ContextMap } from './symbols';
})
export class ContextProviderComponent<T = any> implements OnChanges, OnInit {
private initialized = false;
private provided = new Map();
private _contextMap: ContextMap = {};
private _provide: string | string[] = '';

provided = new Map();

@Input()
set contextMap(map: ContextMap) {
this._contextMap = map || {};
Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-context",
"version": "1.1.0",
"version": "1.1.1",
"repository": {
"type": "git",
"url": "git+https://github.com/ng-turkey/ngx-context.git"
Expand Down
8 changes: 1 addition & 7 deletions src/tests/disposer.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { ChangeDetectorRef } from '@angular/core';
import {
ComponentFixture,
fakeAsync,
inject,
TestBed,
tick,
} from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContextDisposerDirective } from '../lib/disposer.directive';
import { ContextProviderComponent } from '../lib/provider.component';
import { TestDisposerComponent } from './test-disposer.component';
Expand Down
69 changes: 55 additions & 14 deletions src/tests/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NgxContextModule } from '../lib/context.module';
import { ContextDisposerDirective } from '../lib/disposer.directive';
import { TestConsumerComponent } from './test-consumer.component';
import { TestDisposerComponent } from './test-disposer.component';
import { TestDummyComponent } from './test-dummy.component';
import { TestFormComponent } from './test-form.component';
import { TestMiddleComponent } from './test-middle.component';
import { TestProviderComponent } from './test-provider.component';
Expand Down Expand Up @@ -84,8 +85,10 @@ describe('Context Provider & Consumer', function(this: IContextConsumer) {
}).compileComponents();

this.fixture = TestBed.createComponent(TestProviderComponent);
const props = ['target', 'title'];

shouldSyncProvidedProperty.bind(this, 'target');
setProvidedProperties.call(this, props);
shouldSyncProvidedProperties.call(this, props);
}));

it('should work through router outlet', fakeAsync(() => {
Expand All @@ -104,50 +107,88 @@ describe('Context Provider & Consumer', function(this: IContextConsumer) {
RouterTestingModule.withRoutes([
{
path: '',
pathMatch: 'full',
component: TestDummyComponent,
},
{
path: 'test',
component: TestMiddleComponent,
},
]),
NgxContextModule,
],
declarations: [TestProviderComponent, TestMiddleComponent, TestConsumerComponent],
declarations: [
TestProviderComponent,
TestDummyComponent,
TestMiddleComponent,
TestConsumerComponent,
],
}).compileComponents();

this.fixture = TestBed.createComponent(TestProviderComponent);
const props = ['target', 'title'];
setProvidedProperties.call(this, props);

this.fixture.ngZone.run(() => {
// Navigate to base path
this.fixture.debugElement.injector.get(Router).initialNavigation();
const router = this.fixture.debugElement.injector.get(Router);
router.initialNavigation();
tick();

// TestMiddleComponent should not be loaded at first
expect(
this.fixture.debugElement.query(By.directive(TestMiddleComponent)),
).toBeNull();

router.navigate(['test']);
tick();

shouldSyncProvidedProperty.bind(this, 'target');
// All properties should be synced at start
shouldSyncProvidedProperties.call(this, props);

// And properties should be kept in sync later
this.parent.title = null;
this.fixture.detectChanges();
expect(this.child.title).toBeNull();
});
}));
});

type Excluded = 'provided' | 'contextMap' | 'consume';
type Props = Array<
Exclude<keyof TestProviderComponent & keyof TestConsumerComponent, Excluded>
>;

function shouldSyncProvidedProperty(
this: IContextConsumer,
prop: Exclude<keyof TestProviderComponent & keyof TestConsumerComponent, Excluded>,
): void {
// Query component instances
function setProvidedProperties(this: IContextConsumer, props: Props): void {
this.fixture = TestBed.createComponent(TestProviderComponent);
this.parent = this.fixture.debugElement.componentInstance;
this.parent.provided = props;
this.fixture.detectChanges();
}

function shouldSyncProvidedProperties(this: IContextConsumer, props: Props): void {
// Query component instances
this.middle = this.fixture.debugElement.query(
By.directive(TestMiddleComponent),
).componentInstance;
this.child = this.fixture.debugElement.query(
By.directive(ContextConsumerDirective),
).componentInstance;

expect(this.child[prop]).not.toBe(this.parent[prop]);
// Confirm the starting value is different
props.forEach(prop => {
if (typeof this.parent[prop] !== 'undefined')
expect(this.child[prop]).not.toEqual(this.parent[prop]);
});

// Provide property
this.parent.provided = prop;
this.middle.provided = prop;
this.middle.provided = props;

// Detect changes
this.fixture.detectChanges();
tick();

expect(this.child[prop]).toBe(this.parent[prop]);
props.forEach(prop => {
expect(this.child[prop]).not.toBeUndefined();
expect(this.child[prop]).toEqual(this.parent[prop]);
});
}
7 changes: 7 additions & 0 deletions src/tests/test-dummy.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Component } from '@angular/core';

@Component({
selector: 'context-test-dummy',
template: ``,
})
export class TestDummyComponent {}
2 changes: 1 addition & 1 deletion src/tests/test-provider.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ContextMap } from '../lib/symbols';
encapsulation: ViewEncapsulation.None,
})
export class TestProviderComponent {
private _title: string;
private _title = 'Testing';

greeting$ = of('Hello');
target = 'World';
Expand Down

0 comments on commit c82cb79

Please sign in to comment.