Skip to content

Commit 8285915

Browse files
committed
Scenarion #9 completed _now_. Previous commit was #8...
1 parent fcc2596 commit 8285915

File tree

5 files changed

+111
-11
lines changed

5 files changed

+111
-11
lines changed

public/docs/_examples/component-communication/ts/prevent-stealth/src/app/job-service.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
1+
// #docregion
12
import {Injectable, EventEmitter} from 'angular2/angular2';
23
import {Hero} from './hero';
34

4-
export interface JobServiceEvents {
5-
jobPostEvent(): EventEmitter<string>;
6-
jobAssignedEvent(): EventEmitter<Hero>;
7-
}
8-
95
@Injectable()
10-
export abstract class JobBoardFacade implements JobServiceEvents {
6+
export abstract class JobBoardFacade {
117
abstract post(jobRequest: string);
128
abstract assign(hero: Hero);
139
abstract jobRequest();
1410
abstract respondingHeroes (): Hero[];
1511
abstract assignedTo();
16-
abstract jobPostEvent(): EventEmitter<string>;
17-
abstract jobAssignedEvent(): EventEmitter<Hero>;
1812
}
1913

2014
@Injectable()
21-
export abstract class InvitedHeroFacade implements JobServiceEvents {
15+
export abstract class InvitedHeroFacade {
2216
abstract take(hero: Hero);
2317
abstract jobPostEvent(): EventEmitter<string>;
2418
abstract jobAssignedEvent(): EventEmitter<Hero>;
@@ -53,4 +47,5 @@ export class JobService implements JobBoardFacade, InvitedHeroFacade {
5347
this._assignedTo = hero;
5448
this._jobAssignedEvent.next(hero);
5549
}
56-
}
50+
}
51+
// #enddocregion

public/docs/ts/latest/guide/component-communication.jade

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,10 +588,115 @@ figure.image-display
588588
figure.image-display
589589
img(src="/resources/images/devguide/component-communication/job-stolen-ui.png" alt="The job is stolen")
590590

591+
:marked
592+
.alert.is-important
593+
:marked
594+
After a short by-pass (in the next scenario), we'll go on with the code we created in this scenario, so it is worth to make a backup
595+
so that we can return to it easily.
596+
591597
:marked
592598
## #9: Preventing stealth &mdash; using multiple facades
593599

594-
_Content_
600+
Let's fix the issue with `JobService`! If we'd have two separate facades, one for `HeroJobBoard` and another for `InvitedHero`, we would not have this issue.
601+
There are several ways to create two facades that use the same service object in the background. One way is to create two objects &mdash; each with the appropriate
602+
facade &mdash;, and aggregate the singleton `JobService` instance with them. Implictly, these objects would expose only these `JobService` operations that are
603+
required by their facade.
604+
605+
We will apply this method &mdash; with a little twist. We are going to leverage on two great TypeScript features:
606+
1. Abstract classes (more details [here](https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#abstract-classes-and-methods))
607+
1. TypeScript allows to use classes as if those were interfaces (more details [here](http://www.typescriptlang.org/Handbook#classes-advanced-techniques))
608+
609+
Here is the revised map of object communication. It leverages the refactored version of `JobService`, which now provides two facades,
610+
`JobBoardFacade` and `InvitedHeroFacade`, respectively:
611+
612+
figure.image-display
613+
img(src="/resources/images/devguide/component-communication/job-service-facades.png" alt="Communication through new facades")
614+
615+
:marked
616+
Now let's see how these changes are represented in the code of `JobService`:
617+
618+
+makeExample('component-communication/ts/prevent-stealth/src/app/job-service.ts', null, 'job-service-ts', {blk: /(JobService|JobBoardFacade|InvitedHeroFacade)/g})
619+
620+
.alert.is-important
621+
:marked
622+
Although our suggested convention is to create one class per file &mdash; as treated in [Dependency Injection's Appendix](./dependency-injection.html#appendix-why-we-recommend-one-class-per-file)
623+
&mdash; this code is an exception for the sake of easier discussion.
624+
625+
:marked
626+
We declare `JobBoardFacade` and `InvitedHeroFacade` as abstract classes, and mark all of their operations as `abstract`. We altered the definitions of
627+
`jobPostEvent` and `jobAssignedEvent` from a property getter to a function, since property getters cannot be abstract.
628+
629+
Just as many "curly brace" languages, TypeScript does not support multiple inheritance. Of course, it supports implementing multiple interfaces. We utilize the
630+
TypeScript feature that it can handle classes as if those were interfaces: we declare `JobService` so that it _implements_ both `JobBoardFacade` and `InvitedHeroFacade`.
631+
632+
To make `HeroJobBoard` use `JobBoardFacade` and `InvitedHero` to work with `InvitedHeroFacade`, we need to change the constructors of these classes:
633+
634+
code-example(format='linenums', language='typescript').
635+
// hero-job-board.ts:
636+
export class HeroJobBoard {
637+
constructor(private jobBoardFacade: JobBoardFacade) {
638+
// ...
639+
}
640+
// ...
641+
}
642+
643+
// invited-hero.ts:
644+
export class InvitedHero {
645+
// ...
646+
constructor(private heroJobFacade: InvitedHeroFacade) {
647+
// ...
648+
}
649+
}
650+
651+
.alert.is-important
652+
:marked
653+
Evidently, we need to rename the local `jobService` property names in `HeroJobBoard` to `jobBoardFacade`, and in `InvitedHero` to `heroJobFacade` to
654+
make the code sample work.
655+
656+
:marked
657+
We have one more task to complete: we have to change dependency injection so that a _singleton_ `JobService` instance will be injected to the single
658+
instance of `HeroJobBoard` and the multiple instances of `InvitedHero`. We'll do this by changing the `directives` annotation property on `HeroJobBoard`:
659+
660+
code-example(format='linenums', language='typescript').
661+
import {Component, provide, CORE_DIRECTIVES} from 'angular2/angular2';
662+
import {InvitedHero} from './invited-hero';
663+
import {JobService, JobBoardFacade, InvitedHeroFacade} from './job-service';
664+
import {Hero} from './hero';
665+
666+
let jobService = new JobService();
667+
668+
@Component({
669+
/* ... */
670+
providers: [
671+
provide(JobBoardFacade, {useValue: jobService}),
672+
provide(InvitedHeroFacade, {useValue: jobService})
673+
]
674+
})
675+
export class HeroJobBoard {
676+
// ...
677+
}
678+
679+
:marked
680+
Right at the very beginning of the module, we create the single `JobService` instance, `jobService`. Because `JobService` implements both
681+
`JobBoardFacade` and `InvitedHeroFacade`, we can configure the injector to use the single instance of `jobService` to represent both facades.
682+
To achive this, we utilize the `provide()` function with the `useValue` configuration property.
683+
684+
### Why we cannot use `stealJob()` now
685+
686+
Now, the `InvitedHeroFacade` does not offer the `assign()` method, so when you try to compile the `invited-hero.ts` file, the TypeScript compiler raises an error:
687+
688+
code-example(language='text').
689+
src/app/invited-hero.ts(114,28): error TS2339: Property 'assign' does not exist on type 'InvitedHeroFacade'.
690+
691+
:marked
692+
When we work with an editor that has IntelliSense (Such as Sublime or Visual Studio Code), we cannot find `assign()` on the list of available members for
693+
`heroJobFacade`:
694+
695+
figure.image-display
696+
img(src="/resources/images/devguide/component-communication/assign-is-not-offered.png" alt="heroJobFacade members")
697+
698+
:marked
699+
So, using separate facades to expose the services of the singleton `JobService` objects helped us to prevent issues coming from a flaw in the component design.
595700

596701
## #10: Broadcasting messages &mdash; parent and child communicates with an unrelated component
597702

8.49 KB
Loading
55.5 KB
Loading
-19.8 KB
Loading

0 commit comments

Comments
 (0)