πŸ“œ ⬆️ ⬇️

How I stopped loving Angular

Introduction


For many years I have been working with AngularJS and to this day I use it in production. Despite the fact that it’s impossible to call it ideal, because of its historically developed architecture, no one will argue with the fact that it became just a milestone in the evolution of not only JS frameworks, but also the web as a whole.


In the yard of 2017, and for each new product / project, the question of choosing a framework for development arises. For a long time I was sure that the new Angular 2/4 (hereinafter simply Angular) would become the main trend of enterprise development for several years ahead and had no doubt that I would only work with it.


Today I myself refuse to use it in my next project.


Disclaimer : this article is strictly subjective, but this is my personal view of what is happening and concerns the development of enterprise-level applications.


AngularJS



During the years of evolution, most of the flaws in the framework have been eliminated, libraries have been brought to a very stable state, and the size of the community tends to infinity. Perhaps we can say that in the first angular, there is little that can be greatly improved without breaking hundreds of existing applications.


Despite the colossal work and the general acceleration of the framework (especially after 1.5),
its main drawback, I would still call the speed of work. Of course, this is forgivable, given that after so many years, backward compatibility is still maintained at the very least.


Angular



And finally Angular was rewritten from scratch in order to become the main for many future web applications.


Of course, the path to this was long and full of Breaking Changes, but today Angular 4 is stable and positioned as fully production-ready.


One of the coolest things that the new Angular has given us is TypeScript promotion.
Personally, I was familiar with it and worked even before it became the main one for my favorite framework, but many people learned about it thanks to Angular.


TypeScript



I will not dwell on TypeScript, because This is a topic for a separate article.
Yes, and it is written about him more than necessary. But for TypeScript enterprise development, there are a lot of advantages. Starting with the most static typing and scopes and ending with support for ES7 / 8, even for IE9.


The main advantage of working with TypeScript is its rich toolkit and excellent IDE support. In our experience, unit tests with TS have to write significantly less.


Vue



If you are reading this article, then with a 95% probability you already know what it is.


But for those 5% who still do not know - Vue.js is an extremely lightweight (but very rich in functionality) framework that incorporates many good things from both AngularJS and React.


In fact, it looks more like React, but the templates are almost identical to AngularJS (HTML + Mustache).


In reality, it differs from AngularJS very strikingly, but in the superficial sense,
understand how it works, if you already had experience with React or AngularJS, it will be easy.


Prehistory


It was - a big project on AngularJS


My last project, which was recently released in production, we wrote on AngularJS 1.5-1.6.


Despite the existence of a stable version of Angular for a long time, we decided not to migrate to it for several reasons (not so much technical as political ones).
But we used one of the features of the modern web from the very beginning - this is TypeScript.


Here is an example of our component from this project:


import {Component} from "shared-front/app/decorators";
import FileService, {ContentType, IFile} from "../file.service";
import AlertService from "shared/alert.service";

@Component({
    template: require("./file.component.html"),
    bindings: {
        item: "<",
    },
})
export default class FileComponent {
    public static $inject = ["fileService"];
    public item: IFile;

    constructor(private fileService: FileService, private alertService: AlertService) {
    }

    public isVideo() {
        return this.item.contentKeyType === ContentType.VIDEO;
    }

    public downloadFile() {
        this.fileService.download(this.getFileDownloadUrl()).then(() => {
            this.alertService.success();
        });
    }

    private getFileDownloadUrl() {
        return `url-for-download${this.item.text}`;
    }
}

, , TS.
Unit-, 2.


AngularJS , React, , .


, AngularJS. , .


β€” Angular


, Angular 2 ( 4) .


, , . , alpha-RC 0. .


, , .


Angular:


import {Component} from '@angular/core';

import FileService, {ContentType, IFile} from "../file.service";
import AlertService from "shared/alert.service";

@Component({
  selector: 'app-file',
  templateUrl: './file.component.html',
  styleUrls: ['./file.component.scss']
})
export class FileComponent {

    Input() item: IFile;

    constructor(private fileService: FileService, private alertService: AlertService) {
    }

    public isVideo() {
        return this.item.contentKeyType === ContentType.VIDEO;
    }

    public downloadFile() {
        this.fileService.download(this.getFileDownloadUrl()).subscribe(() => {
            this.alertService.success();
        });
    }

    private getFileDownloadUrl() {
        return `url-for-download${this.item.text}`;
    }
}

, .



Angular CLI β€” AngularJS



, Angular 4 Angular CLI


CLI , // .
, Angular. .


, AngularJS, -. (), , . .


CLI " ", React (create-react-app) Vue (vue-cli). , , .


" Angular"


Angular 2 is terrible ( ).


, , ,
. , .


RxJS, .. .


An Ajax request is singular, and running methods like Observable.prototype.map when there will only ever be one value in the pipe makes no semantic sense. Promises on the other hand represent a value that has yet to be fulfilled, which is exactly what a HTTP request gives you. I spent hours forcing Observables to behave before giving up using Observable.prototype.toPromise to transform the Observable back to a Promise and simply using Promise.all, which works much better than anything Rx.js offers.

, RxJS, , Observable, .


, Object.observe :


After much discussion with the parties involved, I plan to withdraw the Object.observe proposal from TC39 (where it currently sits at stage 2 in the ES spec process), and hope to remove support from V8 by the end of the year (the feature is used on 0.0169% of Chrome pageviews, according to chromestatus.com).

, Rx β€” . , , .


TypeScript', , .


, Angular


( , ), .


TypeScript Angular


β€” , TypeScript' Angular.


.


API


TypeScript Angular API.
TypeScript , . , API, Angular , .


:


HttpParams

- Angular HttpParams .
, , Angular , .


:


let params = new HttpParams();
params.set('param', param);
params.set('anotherParam', anotherParam);
...
this.http.get('test', {params: params});

. ?
, TypeScript Angular .


TypeScript


This class is immuatable β€” all mutation operations return a new instance.

, .


:


http
  .post('/api/items/add', body, {
    params: new HttpParams().set('id', '3'),
  })
  .subscribe();

RxJS operator import

, Angular Observable , .


RxJS. , Rx , Observable :


// rx.js
Rx.Observable.create();
vs
// Angular
new Observable()

, Rx + TypeScript + Angular.


RxJS , do:


observable.do(event => {...})

, .


, :


ERROR TypeError: observable.do is not a function

( ) :


import 'rxjs/add/operator/do';

, TypeScript? . .


Router API

, API β€” .


Events

. , , , , . instanceof ( , ):


this.router.events.subscribe(event => {
  if(event instanceof NavigationStart) {
    ...
  }
}


β€” . - :


this.router.navigate(['/some']);
...
this.router.navigate(['/other']);

?


any[]. TypeScript β€” .


, β€” Angular.


AngularJS , enum. , 'some'.


, Angular TypeScript .


Lazy Load

, ,
TypeScript , #


{
  path: 'admin',
  loadChildren: 'app/admin/admin.module#AdminModule',
},

Forms API

β€” Angular : .


, -.


API reactive forms:


//     ?
//  name   c ??
this.heroForm = this.fb.group({
  name: ['', Validators.required ],
});


//     ??
this.heroForm = this.fb.group({
  name: '', // <--- the FormControl called "name"
});


this.complexForm = fb.group({   
  //   compose ?
  //    null ??
  'lastName': [null, Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10)])],
  'gender' : [null, Validators.required],
})

β€” [disabled] ...


, , API


__metadata


TypeScript' Angular .


β€” , , __metadata.


__metadata ///, , .


β€” , .


, AngularJS , @Component:


export const Component = (options: ng.IComponentOptions = {}) => controller => angular.extend(options, {controller});

TypeScript AngularJS .


Angular, , ,
reflect-metadata . .



, TypeScript,
. TS .


β€” Dependency Injection Angular.


, unit . , Java- . , AngularJS , Vue , DI.


Angular , , DI , :


constructor(heroService: HeroService) {
  this.heroes = heroService.getHeroes();
}

TypeScript , , @Inject:


constructor(@Inject(APP_CONFIG) config: AppConfig) {
  this.title = config.title;
}

, @Injectable().


, , , β€” .


Consider adding @Injectable() to every service class, even those that don't have dependencies and, therefore, do not technically require it.
Here's why:

Future proofing: No need to remember @Injectable() when you add a dependency later.

Consistency: All services follow the same rules, and you don't have to wonder why a decorator is missing.

, ?


:


Always write @Injectable(), not just @Injectable. The application will fail mysteriously if you forget the parentheses.

, , TypeScript Angular .


, .



β€” Angular. .


, :


<div [ngStyle]="{'color': color, 'font-size': size, 'font-weight': 'bold'}">
  style using ngStyle
</div>

<input [(ngModel)]="color" />

<button (click)="size = size + 1">+</button>

<div [class.required]="isReq">Hello Wordl!</div>  
<div [className]="'blue'">CSS class using property syntax, this text is blue</div>
<div [ngClass]="{'small-text': true, 'red': true}">object of classes</div>
<div [ngClass]="['bold-text', 'green']">array of classes</div>
<div [ngClass]="'italic-text blue'">string of classes</div>

, .


, [] ().


BindingExample
Properties<input [value]="firstName">
Events<button (click)="buy($event)">
Two-way<input [(ng-model)]="userName">

AngularJS.



, two-way binding
:


Visualize a banana in a box to remember that the parentheses go inside the brackets.


Angular , ,
, .


β€” Vue. , , 6 , .. .


View encapsulation


Angular View encapsulation.


Shadow DOM .


Shadow DOM β€” CSS .
.


Shadow DOM.


CSS :


.first {
  background-color: red;
}
.first .second {
  background-color: green;
}
.first .second .third {
  background-color: blue;
}

Angular :


.first[_ngcontent-c1] {
  background-color: red;
}
.first[_ngcontent-c1]   .second[_ngcontent-c1] {
  background-color: green;
}
.first[_ngcontent-c1]   .second[_ngcontent-c1]   .third[_ngcontent-c1] {
  background-color: blue;
}

.


Vue , :


.first[data-v-50646cd8] {
  background-color: red;
}
.first .second[data-v-50646cd8] {
  background-color: green;
}
.first .second .third[data-v-50646cd8] {
  background-color: blue;
}

, Vue scoped .


, Vue (vue-cli webpack) SASS/SCSS, Angular CLI ng set defaults.styleExt scss. , webpack.


, , .


UI β€” PrimeNG, :


body .ui-tree .ui-treenode .ui-treenode-content .ui-tree-toggler {
    font-size: 1.1em;
}

, .


:


body :host >>> .ui-tree .ui-treenode .ui-treenode-content .ui-tree-toggler {
  font-size: 2em;
}

!important.


PrimeNG , , Angular.



>>> β€” /deep/ shadow-piercing .


"" Shadow DOM .


Angular ,
, /deep/ >>>.


, .
, ::ng-deep β€” shadow-piercing Angular .


(4.2.6 -> 4.3.0), ( NPM ^).


, ChangeLog Angular 4, . β€” .


::ng-deep . , PrimeNG, .


: β€” Shadow DOM .


HTML


, , Angular HTML . .


Angular , , AngularJS HTML () .


AngularJS : <my-component/> .
//.


β€” .


.


,
CUSTOM_ELEMENTS_SCHEMA -



...
const TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
  'base': new HtmlTagDefinition({isVoid: true}),
  'meta': new HtmlTagDefinition({isVoid: true}),
  'area': new HtmlTagDefinition({isVoid: true}),
  'embed': new HtmlTagDefinition({isVoid: true}),
  'link': new HtmlTagDefinition({isVoid: true}),
  'img': new HtmlTagDefinition({isVoid: true}),
  'input': new HtmlTagDefinition({isVoid: true}),
  'param': new HtmlTagDefinition({isVoid: true}),
  'hr': new HtmlTagDefinition({isVoid: true}),
  'br': new HtmlTagDefinition({isVoid: true}),
  'source': new HtmlTagDefinition({isVoid: true}),
  'track': new HtmlTagDefinition({isVoid: true}),
  'wbr': new HtmlTagDefinition({isVoid: true}),
  'p': new HtmlTagDefinition({
    closedByChildren: [
      'address', 'article', 'aside', 'blockquote', 'div', 'dl',      'fieldset', 'footer', 'form',
      'h1',      'h2',      'h3',    'h4',         'h5',  'h6',      'header',   'hgroup', 'hr',
      'main',    'nav',     'ol',    'p',          'pre', 'section', 'table',    'ul'
    ],
    closedByParent: true
  }),
...
  'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
  'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
  'col': new HtmlTagDefinition({requiredParents: ['colgroup'], isVoid: true}),
  'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}),
  'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}),
  'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}),
  'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}),
  'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}),
  'rb': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc'
  ...

β€” ,
, webpack , .
JIT .


, AOT .
--aot, , ng serve . ( ).


,
.


AOT , ,


:


don't use default exports :)

Just place both export types and it works

(AOT )


AOT:


@NgModule({
  providers: [
    {provide: SomeSymbol, useFactor: (i) => i.get('someSymbol'), deps: ['$injector']}
  ]
})
export class MyModule {}

:


export factoryForSomeSymbol = (i) => i.get('someSymbol');

@NgModule({
  providers: [
    {provide: SomeSymbol, useFactor: factoryForSomeSymbol, deps: ['$injector']}
  ]
})
export class MyModule {}

, .


Zone.js


Angular Zone.js.
, . :


core.es5.js:1020 ERROR Error: Uncaught (in promise): Error: No clusteredNodeId supplied to updateClusteredNode.
Error: No clusteredNodeId supplied to updateClusteredNode.
    at ClusterEngine.updateClusteredNode (vis.js:47364)
    at VisGraphDataService.webpackJsonp.../../../../../src/app/services/vis-graph-data.service.ts.VisGraphDataService.updateNetwork (vis-graph-data.service.ts:84)
    at vis-graph-display.service.ts:63
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
    at Object.onInvoke (core.es5.js:3890)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
    at zone.js:818
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Object.onInvokeTask (core.es5.js:3881)
    at ClusterEngine.updateClusteredNode (vis.js:47364)
    at VisGraphDataService.webpackJsonp.../../../../../src/app/services/vis-graph-data.service.ts.VisGraphDataService.updateNetwork (vis-graph-data.service.ts:84)
    at vis-graph-display.service.ts:63
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
    at Object.onInvoke (core.es5.js:3890)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
    at zone.js:818
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Object.onInvokeTask (core.es5.js:3881)
    at resolvePromise (zone.js:770)
    at zone.js:696
    at zone.js:712
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
    at Object.onInvoke (core.es5.js:3890)
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
    at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
    at zone.js:818
    at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
    at Object.onInvokeTask (core.es5.js:3881)

Zone.js , .


.


, .


- , .



UI frameworks


, β€” UI . , β€” -. , UI framework .


, , UI ,
.


UI Angular: https://angular.io/resources ( UI components).


.


Angular Material 2



, Angular Material 2 , Angular .


, , .


, , β€” . - . .


, Angular Material 2 , , , .. , , . multiple-select, .


.


.


FeatureStatus
treeIn-progress
stepperIn-progress, planned Q3 2017
sticky-headerIn-progress, planned Q3 2017
virtual-repeatNot started, planned Q4 2017
fab speed-dialNot started, not planned
fab toolbarNot started, not planned
bottom-sheetNot started, not planned
bottom-navNot started, not planned

Bootstrap



, Bootstrap
ng2-bootstrap () ngx-bootstrap. , CSS, ( modal, datepicker typeahead).


Prime Faces



. ( Tree Table!).


PrimeFaces .. JSF, . PrimeNG ( ). , , .


.


. β€” - . , .


, ,
, PrimeNG .


Clarity



β€” ( ) Clarity vmware.


, , .


UI , CSS .
bootstrap. / .


, ( , , ?).


, datepicker' select2- .
: DatePicker, Select 2.0 ( , , ).


, "Clarity Design System" β€” Angular
( enterprise ). VMware .


, .



, Angular UI .
?


Angular VMware. enterprise? .


, .


Vue UI frameworks



Vue.js :


Element (~15k stars), Vue Material ( Angular Material 2 ), Vuetify ( Material ), Quasar, iView Muse-UI (iView , ).


, , , Vue .
,
, .


?


Clarity , Angular . , Angular, .


, . Vue.js.


webpack vue-cli . , all-in-one,
Vue , Angular.


, UI framework' .


β€” TypeScript,
.


Microsoft . webpack .


React? AngularJS Vue,
v-if, v-model v-for .


, , Aurelia ,
Vue .


, - Angular community - , enterprise framework', . , .


, 4 Angular, .
Vue...


: https://medium.com/@igogrek/how-i-stopped-loving-angular-c2935f7378c4


')

Source: https://habr.com/ru/post/337578/


All Articles