{
"name": "Ilia Idakiev",
"experience": [
"Lecturer in 'Advanced JS' @ FMI",
"Developer / Manager / Owner @ HNS",
"Project Consultant @ ISS"
],
"involvedIn": [
"SofiaJS",
"BeerJS"
],
"latestAchievements": [
"Angular 2 Course @ HackBulgaria"
]
}
Cohesion measures the strength of relationship between pieces of functionality within a given module / class
A measure of how closely connected two modules are.
Images downloaded from: https://gamedevelopment.tutsplus.com
function CoffeeMaker() {
var grinder = new Geinder();
var pump = Pump.getInstance();
var heater = app.get('Heater');
this.brew = function() {
grinder.grind();
heater.on();
pump.pump();
}
}
It's a coding pattern in which a class receives its dependencies from external sources rather than creating them itself.
function CoffeeMaker(grinder, pump, heater) {
this.brew = function() {
grinder.grind();
heater.on();
pump.pump();
}
}
Easy to reuse in different environments => Easy Testing
(wiring)
function main() {
var electricity = new Electricity();
var grinder = new Grinder(electricity);
var heater = new Heater(electricity);
var pump = new Pump(heater, electricity);
var coffeeMaker = new CoffeeMaker(grinder, pump, heater);
coffeeMaker.brew();
}
(maintenance)
function main() {
var logger = new Logger();
var electricity = new Electricity(logger);
var grinder = new Grinder(electricity, logger);
var heater = new Heater(electricity, logger);
var pump = new Pump(heater, electricity, logger);
var coffeeMaker = new CoffeeMaker(grinder, pump, heater, logger);
coffeeMaker.brew();
}
We have to update everywhere we want to use the logger and all of our tests.
(no maintenance)
function main() {
var injector = new Injector(...);
var coffeeMaker = injector.get(CoffeeMaker);
coffeeMaker.brew();
}
We don't have information about the dependencies anymore so we don't need to maintain main function.
var resolvedProviders = Injector.resolve([
{
provide: 'message',
useValue: 'Hello'
}
]);
var injector = Injector.fromResolvedProviders(resolvedProviders);
expect(injector.get('message')).toEqual('Hello');
Injector.resolve returns an array of resolvedReflectiveProviders
import {Injector, Injectable} from 'angular2/angular2';
@Injectable()
class Engine {
}
@Injectable()
class Car {
constructor(public engine:Engine) {}
}
var injector = Injector.resolveAndCreate([Car, Engine]);
var car = injector.get(Car);
expect(car instanceof Car).toBe(true);
expect(car.engine instanceof Engine).toBe(true);
Resolution - flatterning multiple nested arrays and converting individual providers to resolvedReflectiveProviders
Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members. Decorators are a stage 1 proposal for JavaScript and are available as an experimental feature of TypeScript (+ reflect-metadata library).
A Decorator can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
function memoize(target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) {
var hash = {};
var originalMethod = descriptor.value;
descriptor.value = function (...args) {
var str = JSON.stringify(args), data;
if (data = hash[str]) return data;
console.log('calculating ...');
hash[str] = (data = originalMethod(...args));
return data;
};
}
class Util {
@memoize
add(a, b) {
return a + b;
}
}
var util = new Util();
console.log(util.add(1, 2));
console.log(util.add(1, 2));
These are functions that:
class MyClass {
@enumerable(false)
get prop() {
return true;
}
prop2 = 'str';
}
function enumerable(isEnumerable: boolean) {
return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
descriptor.enumerable = isEnumerable;
return descriptor;
};
}
var myClass = new MyClass();
console.log(Object.keys(myClass)); // > ['prop2'];
( decorator )
export interface NgModuleDecorator {
/*Defines the set of injectable objects that are available in the injector of this module.*/
providers?: Provider[];
/*Specifies a list of directives/pipes that belong to this module.*/
declarations?: Array<Type<any>|any[]>;
/*Specifies a list of modules whose exported directives/pipes
*should be available to templates in this module.*/
imports?: Array<Type<any>|ModuleWithProviders|any[]>;
...
/*Specifies a list of components that should be compiled when this module is defined.
*For each component listed here, Angular will create a {@link ComponentFactory}
*and store it in the {@link ComponentFactoryResolver}.*/
entryComponents?: Array<Type<any>|any[]>;
/*Defines the components that should be bootstrapped when
*this module is bootstrapped. The components listed here
*will automatically be added to `entryComponents`.*/
bootstrap?: Array<Type<any>|any[]>;
...
}
Copied from angular @ GitHub
(modified to fit on screen)
( decorator )
export interface Component extends Directive {
/* * **animations** - list of animations of this component
* * **changeDetection** - change detection strategy used by this component
* * **encapsulation** - style encapsulation strategy used by this component
* * **entryComponents** - list of components that are dynamically inserted into the view of this
* component
* * **exportAs** - name under which the component instance is exported in a template
* * **host** - map of class property to host element bindings for events, properties and
* attributes
* * **inputs** - list of class property names to data-bind as component inputs
* * **interpolation** - custom interpolation markers used in this component's template
* * **moduleId** - ES/CommonJS module id of the file in which this component is defined
* * **outputs** - list of class property names that expose output events that others can
* subscribe to
* * **providers** - list of providers available to this component and its children
* * **queries** - configure queries that can be injected into the component
* * **selector** - css selector that identifies this component in a template
* * **styleUrls** - list of urls to stylesheets to be applied to this component's view
* * **styles** - inline-defined styles to be applied to this component's view
* * **template** - inline-defined template for the view
* * **templateUrl** - url to an external file containing a template for the view
* * **viewProviders** - list of providers available to this component and its view children*/
}
Copied from angular @ GitHub
(modified to fit on screen)
( decorator )
/**
* Type of the Inject decorator / constructor function.
* @stable
*/
export interface InjectDecorator {
/**
* @whatItDoes A parameter decorator that specifies a dependency.
* @howToUse
* ```
* @Injectable()
* class Car {
* constructor(@Inject("MyEngine") public engine:Engine) {}
* }
* ```
* When `@Inject()` is not present, {@link Injector} will use the type annotation of the
* parameter.
*/
(token: any): any;
new (token: any): Inject;
}
Copied from angular @ GitHub
(modified to fit on screen)
( providing )
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [
{
provide: 'siteUrl',
useValue: 'http://localhost:8080/users'
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
( injecting and using )
import { Component, Inject } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
url:string;
constructor(@Inject('siteUrl') url) {
this.url = url;
}
}
( using type annotations )
import { Component, Inject } from '@angular/core';
class MyClass {
name:string;
constructor() {
this.name = 'MyClass';
}
getClassName() {
return this.name;
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [{ provide: MyClass, useClass: MyClass }]
})
export class AppComponent {
url:string;
constructor(myClass: MyClass) {
console.log(myClass.getClassName());
}
}
( using type annotations )
import { Component, Inject } from '@angular/core';
class MyClass {
name:string;
constructor() {
this.name = 'MyClass';
}
getClassName() {
return this.name;
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [ MyClass ]
})
export class AppComponent {
url:string;
constructor(myClass: MyClass) {
console.log(myClass.getClassName());
}
}
( using annotations )
import { Component, Inject } from '@angular/core';
class MyOtherClass {
name:string;
constructor() {
this.name = 'MyOtherClass';
}
getClassName() {
return this.name;
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [{ provide: MyClass, useClass: MyOtherClass }]
})
export class AppComponent {
url:string;
constructor(myClass: MyClass) {
console.log(myClass.getClassName());
}
}
( using annotations )
@Component({
...
providers: [ NewLogger,
// Alias OldLogger w/ reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger }]
})
export class AppComponent {
...
}
Suppose an old component depends upon an OldLogger class. OldLogger has the same interface as the NewLogger, but for some reason we can't update the old component to use it.
( Creates a token that can be used in a DI Provider )
/**
* Creates a token that can be used in a DI Provider.
* ```typescript
* var t = new OpaqueToken("value");
* var injector = Injector.resolveAndCreate([
* {provide: t, useValue: "bindingValue"}
* ]);
* expect(injector.get(t)).toEqual("bindingValue");
* ```
* Using an `OpaqueToken` is preferable to using strings as tokens
* because of possible collisions caused by multiple providers using
* the same string as two different tokens. Using an `OpaqueToken` is
* preferable to using an `Object` as tokens because it provides better
* error messages.
* @stable
*/
@Injectable() // so that metadata is gathered for this class
export class OpaqueToken {
constructor(private _desc: string) {}
toString(): string { return `Token ${this._desc}`; }
}
Copied from angular @ GitHub
(modified to fit on screen)
( allows to refer to references which are not yet defined )
/**
* Allows to refer to references which are not yet defined.
*
* For instance, `forwardRef` is used when the `token` which
* we need to refer to for the purposes of DI is declared,
* but not yet defined. It is also used when the `token`
* which we use when creating a query is not yet defined.
*
* ### Example
* {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref'}
* @experimental
*/
export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
(<any>forwardRefFn).__forward_ref__ = forwardRef;
(<any>forwardRefFn).toString = function() { return stringify(this()); };
return (<Type<any>><any>forwardRefFn);
}
Copied from angular @ GitHub
(modified to fit on screen)
import { Component, Inject, OpaqueToken } from '@angular/core';
var URLs = new OpaqueToken('URLs');
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [
{ provide: URLs, useValue: 'http://google.com/', multi: true },
{ provide: URLs, useValue: 'http://yahoo.com/', multi: true }
]
})
export class AppComponent {
url:string;
constructor(@Inject(URLs) urls:string[]) {
this.url = urls.join(',');
}
}
import { OpaqueToken } from '@angular/core';
export const MY_DIRECTIVES = new OpaqueToken('my_directives');
app/shared/my_directives/token.ts
import { Directive, forwardRef } from '@angular/core';
import { MY_DIRECTIVES } from './token';
let direct1Provider = {
provide: MY_DIRECTIVES,
useExisting: forwardRef(() => Direct1Directive),
multi: true
};
@Directive({
selector: '[direct1]',
providers: [direct1Provider]
}) export class Direct1Directive {
doSomething() { return false; }
}
app/shared/my_directives/direct1.directive.ts
import { Directive, forwardRef } from '@angular/core';
import { MY_DIRECTIVES } from './token';
let direct2Provider = {
provide: MY_DIRECTIVES,
useExisting: forwardRef(() => Direct2Directive),
multi: true
};
@Directive({
selector: '[direct2]',
providers: [direct2Provider]
}) export class Direct2Directive {
doSomething() { return true; }
}
app/shared/my_directives/direct2.directive.ts
import { Directive, forwardRef, Inject } from '@angular/core';
import { MY_DIRECTIVES } from './token';
let collectorProvider = {
provide: CollectorDirective,
useExisting: forwardRef(() => CollectorDirective)
};
@Directive({
selector: '[collector]',
providers: [collectorProvider]
}) export class CollectorDirective {
constructor(@Inject(MY_DIRECTIVES) private _directs:any[]) {
console.log('collector', _directs);
}
}
app/shared/my_directives/collector.directive.ts
import { Component } from '@angular/core'
import { DirectivesModule } from './shared/my_directives/directives.module';
@Component({
selector: 'my-app',
template: `
<div>
<div direct1 direct2 collector></div>
</div>
`,
})
export class App {
constructor() {}
}
app/app.component.ts
import { Directive, forwardRef } from '@angular/core';
import { CollectorDirective } from './collector.directive';
import { MY_DIRECTIVES } from './token';
let containerDirectiveProvider = {
provide: ContainerDirective,
useExisting: forwardRef(() => ContainerDirective),
};
@Directive({
selector: '[container]',
providers: [containerDirectiveProvider]
}) export class ContainerDirective {
private collectors: CollectorDirective[] = [];
addCollector(collector:CollectorDirective) {
this.collectors.push(collector);
}
ngOnChange() {
var result = this.collectors.map(col => col.doSomething());
}
}
app/shared/my_directives/container.directive
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<div container>
<div collector direct1 direct2></div>
</div>
`
})
export class App {
}
app/shared/my_directives/container.directive
import { Directive, forwardRef, Inject } from '@angular/core';
import { MY_DIRECTIVES } from './token';
import { ContainerDirective } from './container.directive';
let collectorProvider = {
provide: CollectorDirective,
useExisting: forwardRef(() => CollectorDirective)
};
@Directive({
selector: '[collector]',
providers: [collectorProvider]
}) export class CollectorDirective {
constructor(@Inject(MY_DIRECTIVES) public _directs:any[],
private _parent:ContainerDirective) {
this._parent.addCollector(this);
}
doSomething() {
var result = this._directs.map(direct => direct.doSomething());
return results;
}
}
app/shared/my_directives/collector.directive
import { Directive, forwardRef, Input } from '@angular/core';
import { NG_VALIDATORS, ValidatorFn, FormControl } from '@angular/forms';
var minAmountFnFactory = amount =>
c => c.value > amount ? null : { valid: false };
var minAmountProvider = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MinAmountDirective),
multi: true
}
@Directive({
selector: '[minAmount][ngModel]',
providers: [ minAmountProvider ]
}) export class MinAmountDirective {
private _validator:ValidatorFn;
@Input() set minAmount(value:number) {
this._validator = minAmountFnFactory(value);
}
validate(c:FormControl) {
return this._validator(c);
}
}
app component
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<form>
<input type="number" [minAmount]="200"
[(ngModel)]="amount" #name="ngModel" name="amount">
<div *ngIf="!name.valid">Amount must be > 200</div>
</form>
`,
})
export class App {
amount:number = 0;
}
import { Directive, forwardRef } from '@angular/core';
import { NG_ASYNC_VALIDATORS, ValidationResult } from '@angular/forms';
let emailValidatorProvider = {
provide: NG_ASYNC_VALIDATORS,
useExisting: forwardRef(() => EmailValidator),
multi: true
};
@Directive({
selector: '[emailValidator][ngModel]',
providers: [ emailValidatorProvider ]
})
export class EmailValidator {
constructor(private _userService:UserService) { }
validate(control:Control):Promise<ValidationResult> {
return new Promise((resolve, reject) => {
this._userService.exists(control.value).subscribe((response:any) => {
if(response.exists) return resolve({ userExists: { valid: false } });
else return resolve(null);
}, (error: any) => { console.log(error); })
});
}
}
import { Component } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
let customControl = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCustomFormControl),
multi: true
}
@Component({
...
}) export class MyCustomFormControl implements ControlValueAccessor {
...
writeValue(obj: any) {
//write value to our custom element
}
registerOnChange(fn:any) {
this.propagateChange = fn;
//fn is a function that informs the outside world about changes
}
registerOnTouched(fn:any) {
this.touchedCallback = fn;
//fn a callback that is excuted whenever a form control is “touched”
}
setDisabledState(isDisabled: boolean) {
this.isDisabled = isDisabled;
}
}