Skip to content

Instantly share code, notes, and snippets.

@igogrek
Last active August 14, 2024 20:45
Show Gist options
  • Save igogrek/36d47f7547b679e7dab2bc595e36c0e9 to your computer and use it in GitHub Desktop.
Save igogrek/36d47f7547b679e7dab2bc595e36c0e9 to your computer and use it in GitHub Desktop.
How I stopped loving Angular

I've worked with AngularJS for many years now and still use it in production today. Even though you can't call it ideal, given its historically-formed architecture, nobody would argue that it became quite a milestone not only for evolution of JS frameworks, but for the whole web.

It's 2017 and every new product/project has to choose a framework for development. For a long time I was sure that new Angular 2/4 (just Angular below) will become the main trend for enterprise development for years to come. I wasn't even thinking of working with something else.

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

Disclaimer: this post is strictly subjective, but that's my view on what's going on and this only concerns enterprise-level applications.

AngularJS

For the years of evolution most flaws of the framework were fixed, libraries are now stable and the size of the community is gigantic. Fair to say that it would be hard to improve AngularJS without breaking thousands of existing applications out there.

Despite the great angular-team efforts and overall framework speed up (especially after version 1.5) I would still call the speed the main downside of it. Of course it's forgivable given that even after all these years it's still kinda backward-compatible.

Angular

And now Angular has been completely rewritten from scratch to be the new base for so many new web-applications.

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

One of the coolest things that Angular gave us is TypeScript popularization. I myself knew about it and was working with it for long time ago (from first versions actually). Nevertheless, most of the people knew about it thanks to Angular.

TypeScript

I will not write about TypeScript in details as it's a topic for a separate post and there are already a lot of those in the web. However, for the enterprise development TypeScript gives us so much amazing features. From the type system, member visibility, etc. to ES7/8 support even for IE9.

Main TypeScript upside - very rich toolset and great IDE support. From my experience, there are much less unit tests required if you use it right.

Vue

If you're reading this - 95% chance that you already know what Vue.js is.

But for those 5% others - Vue.js is super-lightweight (but very feature-rich) framework, which combined best of both AngularJS and React.

It's actually mostly similar with React, but templates are nearly the same as in AngularJS (HTML + Mustache).

In reality, it's quite different from AngularJS, but in overall perspective it's very easy to get into and understand if you have React or AngularJS experience.

Intro

The past - big AngularJS project

My latest project, which gone production not a long time ago we did in AngularJS 1.5-1.6.

Despite existence of stable Angular version for quite a long time already, we decided not to migrate on it due to some factors (mostly organizational rather than technical). However, one of features of current web we've used from the start is TypeScript

Here's the 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";

// Custom decorator - info below
@Component({
    template: require("./file.component.html"),
    bindings: {
        item: "<",
    },
})
export default class FileComponent {
    // Ugly duplication, yet much better/stable/encapsulated than using ng-annotate, ng-min etc.
    public static $inject = ["fileService"];
    public item: IFile;

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

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

    public downloadFile() {
        // Promise-based HTTP
        this.fileService.download(this.getFileDownloadUrl()).then(() => {
            this.alertService.success();
        });
    }
    
    private getFileDownloadUrl() {
        return `url-for-download${this.item.text}`;
    }
}

From my point of view, this looks quite nice, even if you're not a fan of TS. It's also easy to test with both Unit and E2E tests (and it is).

Had angular been moving forward and going the same way as React, and if it were a little bit faster, it would still be good idea to write big projects on it.

It's still valid if your team is very experienced in AngularJS. Even so, I think that most of us would like to move forward and choose something modern.

The present - medium-sized Angular project

Therefore, we decided like this and chose Angular 2 (later 4) for our new project few months ago.

Choice seemed quite obvious, given the whole our team had a great amount of expertise with first version. Even more, back in the day, I've worked with alpha-RC versions myself and framework problems were blamed for 0.x version number.

It turned out, sadly, that most of these problems are architecture-related and will not be fixed be fixed in any time soon.

Here's the example of our component in Angular:

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

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

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

    Input() item: IFile;
    // No more ugly string-based $inject
    constructor(private fileService: FileService, private alertService: AlertService) {
    }

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

    public downloadFile() {
        // Now by default HTTP service returns Observable
        this.fileService.download(this.getFileDownloadUrl()).subscribe(() => {
            this.alertService.success();
        });
    }
    
    private getFileDownloadUrl() {
        return `url-for-download${this.item.text}`;
    }
}

A little bit more verbose, yet much cleaner.

Advantages

Angular CLI - the one and only great upgrade over AngularJS

First thing that you'll install after starting Angular 4 app development is Angular CLI.

This CLI is needed to simplify creation of new components/modules/services etc. (among many other useful features). In my opinion, this is the best that new Angular has. Tool is easy to use and really speeds-up development process.

This is what we were really missing in AngularJS. In the absence of such toolset, everyone was reinventing the wheel every time. Insane amount of different seed applications (starters), hundreds of different approaches to the same stuff, anarchy. Now it's not a problem anymore and all of us have a common way to start/build/test angular application.

Of course, CLI has some weaknesses especially in terms of configuring it for custom/non-standard needs. Yet it's much more functional than same tools for React (create-react-app) or Vue (vue-cli). Second, however, becoming better every day now.

Disadvantages or "How I stopped loving Angular"

At first I wasn't going to write another hater post like Angular 2 is terrible.

However, despite above post being written for quite an old Angular version already, it's spot on most of the points. I might say that writer might have been too soft sometimes.

One point, which I don't completely agree on, is about RxJS, simply because it's so powerful:

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.

In reality, thanks to RxJS, if you start to think of any data as stream it tends to be very easy to manipulate.

Observables can be very useful sometimes. Yet the sad truth is that we will not see a native 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).

Moreover, even with all of the great features Rx gives us - making it framework core is not the best approach.

I think that people like me (who really like Rx) would've easily integrated it themselves if needed, instead of forcing it as default. This wasn't a problem for me, but a lot of people I know have a hard time understanding Observables, Subjects, etc. All of that on top of general Angular complexity seem to be very harsh on framework newcomers (most of those just cast HTTP resulted Observable to Promise).

Another point, which I don't agree on, is TypeScript - it's really an amazing language/tool, but that's below.

Post above is highly recommended to check, especially if you already use Angular or just planning to

Nevertheless, I'll write some of my own thoughts on it, not pointed out in that post.

TypeScript in Angular

The most painful disappointment for me - is what happened with TypeScript usage in Angular.

Some of the bad examples below.

Poor API design

One of the main problems of TypeScript usage in Angular I consider API design. TypeScript itself is ideal for making most stable, strict code without the possibility to make the wrong move. It's basically created for making public API's, yet Angular team did all they can not to use this as an advantage.

Examples:

HttpParams

For some reason Angular team decided to make HttpParams immutable. Don't get me wrong here, immutability is really great, but only if it's a general approach. If you think that most of the classes in Angular are immutable - you are wrong.

So this code for example:

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

Will not any parameters to the request. Why? There are no TypeScript or Angular errors or any kind of warnings. However, there are no parameters for your GET.

Only opening the class itself in TypeScript you can find the comment (with a typo btw):

This class is immuatable - all mutation operations return a new instance.

Which is, of course, non-intuitive at all.

And here's all of the info in the docs:

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

I have many complaints for the new Angular router, but API is the main concern.

No more named routes

For some strange reason Angular team decided to remove support for the named routes. It's basically a string name for the route, to be later easily used without bothering with the url.

In our AngularJS app, this was essential for a few reasons:

  • easy to use for programmatic redirection in controllers
  • able to use enum for routes, to search and refactor the whole application easily (Alt+F7 instead of global search by string)
  • saved us a whole lot of pain, when after having a hundred of existing routes, there arise a new root route requirement (virtual) - just a matter of changing one route config file, not breaking any existing URL's, thanks to the route names

I don't know why is this (much needed) feature was removed, but some people had to implement it themselves.

Events

Now to work with route parameters we have to subscribe to the router events. Ok that's fine, but you only can subscribe to all of them, even if you only need one. And you'll have to check using instanceof (which is again a new approach, different from most of other places):

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

If router events may look ok for some people, the idea of making query params an Observable looks very strange me:

this.activatedRoute.queryParams.subscribe(params => {
    const val1 = params['key'];
    const valN = params['keyN'];
});
Navigation

Another strange decision was to make all of the router interaction based on the commands, even an Array of them. So most popular and simple routes will look like this:

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

Why is this bad?

Because in this case commands have a signature of any[]. For those who are not familiar with TypeScript - this basically disables its type-checking features telling the compiler there can be anything here.

That is when the routing is the most loosely coupled part in Angular.

For example, in our AngularJS app, we tried to make routes typed as much as possible, even using enums instead of simple strings. This greatly helps finding routes in a big codebase and highly helps with refactoring. Therefore, in the case above instead of global search for 'some' string you can just press Shift + F6.

Again, Angular doesn't use this great TypeScript advantage.

Lazy Load

This section can be another post, but I would like to point out that any TypeScript features are ignored of module name is specified like a string after #

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

First of all - there are two form types in angular: template-driven and reactive.

Of course, they work completely differently.

Nevertheless, my main concern is about reactive forms API :

// What is the empty parameter and why is it needed?
// Why name is an array with a validator??
this.heroForm = this.fb.group({
  name: ['', Validators.required ],
  street: ''
});

or some examples from the docs:

this.heroForm = this.fb.group({
  // Empty field is actually a FormControl called "name"?
  name: '', 
  ...
  // ??
  secretLairs: this.fb.array([])
});

Not even mentioning stuff about combining both form types - you can't just use attribute binding like [disabled] for reactive forms...

These are just some of the examples of poor API design, there are much more, but I think it's ok for this section

__metadata

Sadly, TypeScript usage in Angular is heavily based on Decorator usage.

Decorators are great, but in runtime there's a great part missing in the current web (ES7 quite is far) - __metadata. It is disabled by default and only emitted if there's a compiler option specified.

__metadata just stores the information about class/type/method/parameters etc., marked with the decorator. This is needed to get this information in runtime. Think of reflection in Java-like languages.

Without metadata, you can still use decorators - at compile time, but there's not much profit in this case.

Nevertheless, in our AngularJS app we've used this decorators, like @Component:

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

It just wraps our TypeScript classes in AngularJS component objects and makes them controllers.

However, in Angular, despite being this language feature still experimental, it became framework core. This means that you'll have to use reflect-metadata in absolutely any case. Very arguable decision.

Abstractions

Amount of internal classes, abstractions and a lot of TypeScript-related tricks, do not help with community adoption and may even make a bad impression about TypeScript iteslf.

The main example of such problems is the Dependency Injection in Angular.

The concept is great, especially for unit testing. However, in reality it seems that there's not much use of making something Java-similar on the frontend. We've actively used this in our AngularJS application for a long time, but after working some time with Vue component testing, I've really started doubting DI usefulness.

That goes for Angular modules too. In past I've thought that they are very great in making the application modular, IoC and all that. It seemed, for example, very useful for Lazy Loading. In reality - whole minified Vue.js is about 60KB, and can be decoupled without any problems and modules at all. So do we really need all of that module boilerplate?

Back to DI. In Angular most of the common dependencies, like services, DI will look quite simple, with constructor:

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

But this will only work for TypeScript classes, so if you want to add a constant, you'll have to use @Inject:

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

Ah, services which you inject, must be annotated with @Injectable().

But not every service, only those who have own dependencies, otherwise you can omit the decorator.

Why not make them mandatory if it's recommended to always do anyway:

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.

Another great quote from official docs about parentheses:

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

Overall, this leaves the impression that TypeScript is definetely used worng in Anguar.

Again, I will point out that the language itself really helps in development.

Template syntax

Template syntax is the main complaint to the Angular by the most developers. And it is very objective point.

Example of many different directives with different approaches/usages:

<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>

Initially developers positioned the new syntax as an escape from directive overflow in AngularJS.

The promise was about only having two things: [] and ().

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

Unfortunately, in reality, the amount of directives is not much less than AngularJS's.

In addition, here's the great example about two-way binding syntax from official docs:

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

Documentation

I don't really see a point of describing Angular documentation. It's so amiss that you just have to read it to understand that it is not ok for such an enterprise framework.

It's also worse than AngularJS docs as there's no more a version selector - only major versions like 2, 4 and 5 are there. And there ARE a lot of breaking changes between.

On the contrary - Vue docs. Not only they are written in much cleaner and verbose way, they are also available in 6 languages like russian.

RxJS

In addition, Angular docs don't have any sensible page with information on Rx and Observable or how to work with it. There's no links to the official RxJS docs too. Even though Rx is a core part of the framework and the creation of Observable is different:

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

View encapsulation

Angular has this concept of View encapsulation.

It's basically Shadow DOM emulation or the usage of the native support of it.

The Shadow DOM itself is great and potentially lets you even use different CSS frameworks on one page, in different components, without style overlap.

However, native support is quite low today.

Therefore, by default Angular emulates the Shadow DOM.

Here's a simple CSS example for component:

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

Angular will process it to this:

.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;
}

Dunno why it's done like that with adding custom attribute to every rule.

Vue has the same possibility, but this looks much cleaner:

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

Not even mentioning, that Vue doesn't force this by default and enabling it is as simple as adding a scoped to your style tag.

Please also note, that Vue (vue-cli webpack) lets you easily switch between CSS/SASS/SCSS in the same way, but Angular CLI requires running stuff like ng set defaults.styleExt scss. Not sure why, because under the hood it uses the same webpack, and could've used both extensions without any problems (webpack doesn't really care about the extension, it's just a matter of additional step in its internal pipeline).

Nevertheless, that's not the main problem, real one we've encountered when started using external components.

In our case, we've used one of the most popular UI framewokrs - PrimeNG, which use such selectors sometimes:

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

These selectors by definition already have a higher priority, than component styles, which uses this external element.

To fix this you'll have to write something like this:

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

Sometimes it's only doable via the fabled !important.

Of course, all of this is related to PrimeNG, but that's the problem you'll have too if you will actually start using Angular.

Few words on stability

In the example above we've used >>> - as the /deep/ it's an alias for the shadow-piercing selector. It allows us to "ignore" Shadow DOM for some rules and may be invaluable for some of the external components.

In the one of the recent versions of Angular, developer team decided to deprecate both /deep/ and >>>, as per new standard.

No build errors or warnings were emitted, only the selectors stopped working. As it turned out, now only ::ng-deep works - the analogue of the shadow-piercing selector in the Angular universe.

The update wasn't the major version one (4.2.6 -> 4.3.0, special thanks to NPM for versions with ^), just one day a lot of our styling/layouts broke for some reason.

Of course, not everyone on our team reads Angular ChangeLog every day, and may have missed the latest standard/trend too. Therefore, at first we searched for a problem in our own styles - we've had to spend quite some time and brains to find and fix this issue.

Moreover, soon ::ng-deep will stop working too. I don't really know how to fix the styles of not-so-good frameworks, like the mentioned PrimeNG, without it.

Our conclusion on this: default setting - Shadow DOM emulation, creates more problems than it solves.

Custom HTML parser

This can also be a separate post, but making it short: Angular really wrote its own HTML parser. Most likely, the main case for this was the case-sensitivity.

There's no reason to wage another holy war about Angular not being standard, but a lot of people thinks that this is a strange idea, because for AngularJS simple HTML (case-insensitive) was more than enough.

With AngularJS, there could be a situation like this: you've created some <my-component/> and didn't create a test for it. After some time the module with component was removed/refactored/etc.

Anyway - <my-component/> isn't displayed now at all.

Now the parser determines the unknown tags and throws an error breaking the whole build.

But now any external/web component requires either disabling this checking at all, or enabling of the CUSTOM_ELEMENTS_SCHEMA which allows anything with - in it.

You can check the sources yourself:

...
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'
  ...

Key part here - all of these errors happen in browser console at runtime, no it will not fail your webpack build, but you will not see anything aside from the white screen. That's because by default JIT compiler is used.

This can be fixed by precompiling the templates via another, AOT compiler, Just run with --aot flag and that's it. But no, there's a bad part here too: this badly works with ng serve and slows down the compile time even more (which is very slow to start with, many times slower than Vue). Seems that's why it isn't enabled by default (which it should be).

Existence of two differently working compilers may sound dangerous, and really causes problems all the time.

We had many different AOT-related errors, including some that are still open. Like you {can't use default exports](angular/angular#11402):

Also, note the elegant solutions:

don't use default exports :)

Just place both export types and it works

Or a lot of problems like this

AOT is not always able to understand closures, so the code below will cause strange compiler errors:

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

So you'll have to write the code in more primitive and compiler-friendly manner:

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

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

In addition, we've noted that the errors in the templates are usually non-informative at all, which is a shame, since it's harder to search for those in HTML and IDE support isn't the best.

Zone.js

Another great concept, which is there in new Angular, is Zone.js. It allows monitoring the execution context for the asynchronous actions. However, these stack-traces are super big and are very hard to understand, especially for newcomers. Here's an example:

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)

Some people do like it, but in our project, we didn't yet get much value from it. I hope this will later help in production, but not sure.

Moreover, while it's still long way to production - we have to develop, and the amount of errors in console is usually more than one, so the search for the potential issue is always complicated due to such stack-traces.

Not even mentioning, that sometimes, Chrome console isn't enough for all of them at all. In addition, the cause of the problem may be somewhere far above in the console, if it happens a lot.

UI frameworks

Another section, which is actually not the point about framework itself, is the very low amount of existing UI components/frameworks. Sadly, I think that writing most of the UI components from scratch - is impossible for most of the web developers/small teams. It's usually sane (and much simpler, faster) to get an existing UI framework instead of creating own trees and grids.

And yes, I know that it is not a good way to select a front-end framework by the UI components, but most of the time it is vital for the real development.

Here's the list of the UI frameworks for Angular: https://angular.io/resources (UI components section).

Let's look at the most popular free and open-source ones.

Angular Material 2

Most of the big hopes I had were for Angular Material 2 due to it's being developed by the Angular team and would definitely match the Google guidelines.

Sadly, even though it exists for quite some time already, component set is rather small.

At the time when I've started writing this post - there weren't any grids (there were even less components when we were selecting the UI framework for our project) . And not a long time ago it's finally arrived. Yet the functionality is quite basic anyway.

I think, that Angular Material 2 will only be ok for smaller or, best case, medium-sized projects, because even now there's no trees, for example. In addition, most of the time you need stuff like multiple-select, which there is none either.

Very scarce documentation and low amount of examples is worth noting too.

Last point is the sad feature plan.

Feature Status
tree In-progress
stepper In-progress, planned Q3 2017
sticky-header In-progress, planned Q3 2017
virtual-repeat Not started, planned Q4 2017
fab speed-dial Not started, not planned
fab toolbar Not started, not planned
bottom-sheet Not started, not planned
bottom-nav Not started, not planned

There's also a Covalent UI - but I really recommend you to stay away from it, given that it's just an extension for the Angular Material. And not anywhere near as stable. There are also 2 different grids now - one official form Angular, and another from Covalent (I don't think this is good at all).

Bootstrap

For the same reasons as the above, I will not describe Bootstrap-based frameworks like ng2-bootstrap (little better) and ngx-bootstrap. They are not bad at all, but the simplest stuff you can do yourself with Bootstrap CSS, and there are no complex components here anyway (though some devs may be ok with just modal, datepicker and a typeahead).

Same goes for the Semantic which I personally really like, but it is still quite basic.

Prime Faces

The most popular framework today and the one with the most complex components is PrimeNG. There are grids, trees and even Tree Table!

At first, I was really skeptical regarding PrimeFaces, because I've had an old JSF experience with them and a lot of painful memories. They even look the same (not very modern for the free themes you can get). However, at the time of starting our project, there weren't any good alternatives, so I would still say a Thank You to the development team. They've created a vast toolset in a short term, which generally does the job.

Nevertheless, the amount of problems we've had with it is very huge.

Documentation is quite useless sometimes. Forgot to add some module - something just broke without any kind of error. There's a lot of debugging required, which leads to the sources that have no comments.

Overall, despite having so many components, I wouldn't recommend using PrimeNG as a UI component base for your project.

Clarity

The ray of hope here - is young (created less than a year ago) library Clarity from vmware.

Component set is great, documentation is clean and it just looks nice.

Framework not only has it's own UI components, but a CSS guidelines too. Something like an own Bootstrap (some styles like grid/buttons/etc. are somewhat similar). Thanks to that there a consistent and quite nice/minimalistic component look.

Grids are very functional and stable, and the sources speak for themselves (wow, writing of the unit tests is allowed here?).

However, for now forms are very basic, there's no datepicker or select2-like component yet. It's being worked on right now: DatePicker, Select 2.0 (as always design is slick, and even if it's progressing slowly, we surely be glad with the result).

I would say, that "Clarity Design System" - is the only cause I still believe in Angular. It's also the only framework that wouldn't be a shame to use for enterprise development. After all, VMware is the serious maintainer and there's a hope for a bright future.

We've just started using it, and will probably run into a lot of problems, but for today we're fully satisfied with it and it works just fine.

But it is the only one

Yes, I think that there's only one worthy Angular framework today. Question: What does this mean?

Answer: Development of such frameworks for Angular is only possible with big companies like the mentioned VMware. It requires a lot of skills, time and resources. Do you really need this kind of enterprise? Please think about it.

Now let's look at what's going on with one of the main competitors (much younger btw).

Vue UI frameworks

For the reference, here's the list of existing Vue.js frameworks with existing grid components:

Element (~15k stars), Vue Material (much younger than Angular Material 2, yet already much more powerful), Vuetify (Material again, and many components already too), Quasar, Chinese-only frameworks should be mentioned too: iView and Muse-UI (iView looks very nice, but the docs aren't good at all).

This is a simple, yet obvious, example, that creation of such components is much easier with Vue. This even allows you to select one of the many components, instead of hoping for the one, which is supported by some huge dev team.

Our conclusion

Thanks to Clarity, there's hope that our Angular project will become much better in future. However, for us it became clear, that all of the problems with Angular, and all of those complications aren't making any benefit, even for a large project.

In reality, all of this just increases the required development time, without making it cheaper to support or refactor the code. So for our next project we chose Vue.js.

It's just a matter of starting up the base webpack template for the vue-cli to understand the speed of the library. Even though I've always been a fan of the all-in-one frameworks, Vue does most of the stuff Angular does (without any problems/much effort).

And, of course, the amount of UI frameworks for it is a strong advantage too.

Vue already have some of the stuff, which isn't there in Angular for long - like Server Side Rendering with Nuxt.js, or cross-platform with Weex (by the way is supported by the great Alibaba).

One thing that I do miss currently - better TypeScript support, because for the years of using it saved us from quite a lot of pain.

Hopefully Microsoft guys will soon merge it. And afterwards it'll be in the webpack template.

Why not React? After working with AngularJS, our team was much easier to get into Vue, due to all of the v-if, v-model and v-for being very familiar.

I myself like the Aurelia (not everything about it though), but it isn't really popular at all right now. And compared to Vue's explosion-like growth, it seems to be completely unknown to the community.

I hope, after one-two years Angular will get rid of excesses, fix the main issues and will finally become the enterprise framework, it was supposed to be. However, today I recommend you to look for other, more lightweight and elegant solutions.

Trust me, after working with Angular for 4 years, it was very hard to abandon it. But after having a taste of Vue...

@Dagge1
Copy link

Dagge1 commented Sep 21, 2020

I am a Senior full stack software architect, programming with both latest Vue and Angular V.10 frameworks. I use both upon client requirements, but I personally prefer Vue.js It is as light or as powerful as you need, it's light on resources, it uses full javascript (no transpiling typescript into js code like Angular does), it is un-opinionated so you can use your own modules of preference (Axios http request module is far better than Angular in-house rx Obeservable version). Using asynchronous Promise and async/await principles in Vue are far better than using rx/Observable in Angular which are fuzzy, unclear, undocumented and unnecessary complicated.

As a conclusion, both are very good, neither is easy to learn (if you want to use full potential). If something bothers me with Angular is its payload which is huge, and it's use of typescript concepts that will at the end be transpiled back to javascript. So a few lines of ts code that determines data type will be transpiled to 50 lines of js code, which is.. err.. loosely typed language, i.e. it doesn't specify what type of information will be stored into a variable. So you feel like you have done merry-go-around trip, while you could easily restrict data type at user input phase with javascript alone.

That's what I call inefficiency. Another weird moment is that Angular uses typescript class, which is actually a syntactic sugar for describing prototypal inheritance in javascript, it's there basically for luring in OOP programmers from other languages. Marketing gimmick. Best thing is that class in javascript is completely different concept than class in OOP languages, so OOP programmers are actually cheated into thinking it is the same concept.

At the end I will repeat, both frameworks are powerful and good for developing modern async apps and systems. React is ok but it is only a library, not a full framework. The choice is yours, it is best to learn both Angular and Vue but this will force you to invest many hours (or months, or years) and a lot of effort and will power. After all, it takes 10 years for one to fully master any discipline or a knowledge. If you really don't like it, don't do it because your life will be pain.

@Waxolunist
Copy link

I think at first something is truly wrong in the conception of the question per se. Before deciding for a framework ask first if you need a framework at all.
And if not, how to shape your components.
Probably, simple custom elements are enough for you. They have natively a lot of the frameworks stuff already built in. When using a decent base element like lit-html you have a good starting point without framework at all.

@oadrian2
Copy link

oadrian2 commented Jan 13, 2021

I've been working with Angular8-11 now and React 16+. I've also done Web Development for more than two decades now. Angular2 reminds me a lot of ASP.Net WebForms. I remember liking parts of WebForms, especially at the time, but I would always end up in these weird places trying to break down life cycle methods so much so that SILVER is burned into my memory.

WebForms was also overly engineered and designed from a .Net perspective where Angular feels like it's written from a Java perspective. Both rely heavily on OOD which, honestly, is to their detriment. It makes for a nice conversion when moving from .Net or Java, but feels alien when coming from JQuery or Vanilla JS.

Fundamentally, my problem with Angular ended up being the same problem as WebForms. They both pretend you're not writing a webpage. There's so much abstraction between the HTML+JavaScript+CSS the abstraction was bound to be leaky. Your HTML doesn't look a HTML. Instead of:

<div data-xyz="" class="" onclick="">...</div>

you get

<MyComponent [attr.data-xyz]="blah" (click)="onClick($event)">...</MyComponent>

There's so much magic you have to know there. The special decorators ([], (), [()]), the magic $event variable, the special attr. prefix. There's so much that's just different from the baseline technology it's wrapping. Sometimes, unnecessarily so. Which leads me to a suspicion, which is that the Angular authors didn't really like the web technologies they were wrapping. Angular goes out of its way to pretend you're not writing HTML and JavaScript.

@hhvdblom
Copy link

Just started a new MVC application with bootstrap 5 with css grid, so I don't use the grid system of bootstrap. Bootstrap 5 has no jQuery so that's OK. The styling is good of Bootstrap. So let's see how this works out. I use javascript classes like modules. I use those classes like singletons. Sweet is: FlexNav.initialize(). FlexNav is an imported class in main.js. initialize() is a static function that initializes the class. I don't return new because I have no further use of the class in main.js. I also will use a javascript file that will change all links in Ajax calls, barba.js maybe and voila I have a SPA like application without the shit of Angular etc. They try to solve a problem that's not a problem. Server site I will have a much easier authentication solution as with Angular etc.

@tgrushka
Copy link

tgrushka commented Jan 16, 2023

It is sad that in 2023, in Angular 15, you still cannot natively use system environment variables in environment.*.ts. There is no excuse for it. Other frameworks allow you to do so. Who in the world develops an app without environment secrets? Who in their right mind wants to use plugins for this basic functionality that don't even work, are just unintuitive and have poor documentation (ng-process-env), or depend on rewriting files at build time (ngx-build-plus), or require you to prefix all variables with NG_APP_ (@ngx-env/builder)? I want to do (but can't):

export const environment = {
    production: false,
    apiUrl: 'https://localhost:8080/',
    clientId: process.env.GOOGLE_CLIENT_ID,
}

Oh yeah, you totally could eject your webpack config and undermine all benefits of the ng CLI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment