Как переписать крупный проект на Angular и (не) впасть в депрессию

Илья Таратухин / @darkilfa


  • 7 years in frontend
  • 5,5 years in 2GIS
  • Knockoutjs -> own framework -> React
  • 1,5 years in Wrike
  • Dart + Angular
  • Last year work on UI-kit

What is the plan?

  • Project and its problems
  • What's wrong with developers?
  • What's wrong in Angular?
  • Lifehacks

What is Wrike for FE developers?

  • Project with 10 years history
  • Legacy code running on ExtJs + bicycles
  • 50+ FE developers
  • New code runs on Dart + Angular

Angular newcomers

What can go wrong?


  selector: 'custom-input'
class CustomInput {
  @Input() value: String;
  @Input() label: String;
  @Output() change: EventEmitter<any>;

  @Input() size: String; // s, m, l, xl
  @Input() type: String; // light, invisible
  @Input() fieldType: String; // email, password
  @Input() isReadonly: bool;
  @Input() isAutocomplete: bool;

  @Output() focus: EventEmitter<any>;
  @Output() blur: EventEmitter<any>;

  focus(): void {}

// custom input template
<input class="input-text"

<label *ngIf="isLabelNotEmpty"
	{{ label }}

  selector: 'input[custom-input]'
class CustomInput {
  @Input() size: String; // s, m, l, xl
  @Input() skin: String; // light, invisible
// Usage in template
<input-meta [label]="labelText">
  <input custom-input
  [size]="'m'" [skin]="'light'"/>



Don't wrap native elements


  Tooltip content

class Tooltip {
  @Input() trigger: String;
  @Input() position: String;
  @Input() target: Element;

class Tooltip {
  @Input() trigger: String;
  @Input() position: String;
  @Input() target: Element;

  open(): void {}
  close(): void {}

  @Output() onOpen: EventEmitter<any>;
  @Output() onClose: EventEmitter<any>;

class Tooltip {
  @Input() position: String;
  @Input() target: Element;

<tooltip *ngIf="isOpened"
  Tooltip content


  • Write stateless components
  • Write simple components

Angular, any problems?

How Angular render work?


What does CD do?

  • Every time on NgZone.onStable
  • For each component
  • For each binding
  • Checks prevVal != nextVal

When can CD brake your app?

  • Your application is big
  • You have data calculation on render
  • You listen sockets in Angular Zone

How to fix it?

  • Subscribe on events out of zone
  • Use OnPush CD strategy

Default change detection

  • Each zone update
  • Checks all tree from root
  • Checks all bindings

OnPush change detection

  • Each zone update
  • Runs CD if @Input change
  • Runs CD if event was in component`s zone
  • Doesn't check subtree if component has no changes
  • You need immutable state tree

How to control CD manually?

  • markForCheck - for mark subtree up to root
  • detectChanges - for trigger CD
  • detach - for skip CD cycles
  • reattach - cancels detach

Dynamic components



<task-list [tasks]="tasksModel">
    *ngFor="let item of tasksModel.items"

  selector: 'task-list',
  template: '<ng-content></ng-content>'
class TaskList implements AfterContentInit {
  @Input() tasks: TasksModel;
  @ContentChildren(Task) tasks: QueryList<Task>;

  ngAfterContentInit() {
    // contentChildren is set


  • Doesn't help with complicated structures
  • Looks like crunch



<ng-template ref-taskTemplate
  <task [model]="item">

// Task list template

  "taskTemplate; context: taskItem">

Avoid dynamic components


Use sandbox

Angular can be really bad guy

  • Too much change detection
  • Need to trigger change detection manually

Do you have it on other tech stack?

Use external library!


  • Don't wrap native elements
  • Stateless components
  • OnPush
  • Subscriptions out of zone
  • Avoid dynamic components
  • Use sandbox
  • Don't write bicycles

Thank you

- Follow me: @darkilfa
- : @wriketeam