
Good afternoon, colleagues. We are considering whether to update the book "
Angular and TypeScript. Website Building for Professionals " by Jacob Fine and Anton Moiseyev.
The new edition is coming out this fall and includes material on Angular 5 and 6.
At first we thought to publish material about the Ivy engine, which will probably be the most interesting innovation in Angular 6, but then we stopped on a more overview publication from Cedric Exbrayat (the original was released in May).
In Angular 6, there are quite a few serious innovations, and the most important of them cannot be called a feature: this is Ivy, a new rendering engine. Since the engine is still experimental, we'll talk about it at the end of this article, and start with other new features and revolutionary changes.
Tree-shakeable providers')
Now there is a new, recommended way of registering the provider, directly in the decorator
@Injectable()
, using the new attribute
providedIn
. It takes
'root'
as the value of any module of your application. When using
'root'
object being injected will be registered in the application as a loner, and you will not need to add it to the providers in the root module. Similarly, when using
providedIn: UsersModule
object being
providedIn: UsersModule
is registered as the
UsersModule
provider, and is not added to the module providers.
@Injectable({ providedIn: 'root' }) export class UserService { }
This new method was introduced for better removal of non-functional code in the application (tree-shaking). Currently, the situation is such that the service added to the providers of the module will be in the final set, even if it is not used in the application - and it’s a little sad to admit. If you use a lazy load, you can fall into several traps at once, or find yourself in a situation where the service will be listed in the wrong set.
Such a situation in applications is unlikely to happen often (if you write a service, then use it), but in third-party modules sometimes we offer services that we don’t need - as a result we have a whole heap of useless JavaScript.
So, this feature will be especially useful for library developers, but now it is recommended to register injected objects in this way - this also applies to application developers. The new CLI now even applies the default
providedIn: 'root'
scaffolding when working with services.
In the same vein, you can now declare the
InjectionToken
, directly register it with
providedIn
and add the
factory
here:
export const baseUrl = new InjectionToken<string>('baseUrl', { providedIn: 'root', factory: () => 'http://localhost:8080/' });
Note: this also simplifies unit testing. For the purposes of such testing, we are accustomed to register the service with the test module providers. Here’s how we did before:
beforeEach(() => TestBed.configureTestingModule({ providers: [UserService] }));
Now, if the UserService uses
providedIn: 'root'
:
beforeEach(() => TestBed.configureTestingModule({}));
Just do not worry: all services registered with
providedIn
are not loaded into the test, but are lazily instantiated, only in cases when they are really needed.
RxJS 6Angular 6 now uses RxJS 6 internally, so you will need to update the application to reflect this.
And ... RxJS 6 changes the approach to import!
In RxJS 5, you could write:
import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/map'; const squares$: Observable<number> = Observable.of(1, 2) .map(n => n * n);
Pipeable operators appeared in RxJS 5.5:
import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; import { map } from 'rxjs/operators'; const squares$: Observable<number> = of(1, 2).pipe( map(n => n * n) );
And in RxJS 6.0, imports have changed:
import { Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; const squares$: Observable<number> = of(1, 2).pipe( map(n => n * n) );
So, one day you will have to change the imports within the entire application. I am writing “once,” not “right now,” because rxjs-compat library was released in RxJS, allowing you to upgrade to RxJS version 6.0, even if the old versions are still used in your entire application or in one of the libraries used syntax.
The Angular team wrote
a whole document on this topic, and it is absolutely necessary to read it before migrating to Angular 6.0.
Notice: there is a very cool tslint ruleset called
rxjs-tslint
. There are only 4 rules in it, and if you add them to the project, the system will automatically migrate all your imports and the RxJS code, and this is done with the simplest
tslint --fix
! After all, if you still do not know,
tslint
has a
fix
option that automatically fixes all errors it finds! It can be used even more simply: install
rxjs-tslint
and run
rxjs-5-to-6-migrate -p src/tsconfig.app.json
. I tried
rxjs-tslint
on one of our projects, and it worked quite well (run it at least twice to also minimize all imports). Check out the README of this project if you want to learn more:
github.com/ReactiveX/rxjs-tslint .
If you are interested in further studying RxJS 6.0, I recommend the
following Ben Lesch
report on ng-conf.
i18nThe most important i18n perspective is the ability to do “i18n at runtime” without having to build an application separately for each local point. So far this feature is unavailable (there are only prototypes), and its operation will require the Ivy engine (more about it below).
Another i18n change has already taken place and is available. The currency channel has been optimized in the most efficient way: it now rounds all currencies not up to 2 characters, as before, but to the required number of characters (for example, up to 3 in the case of a Bahraini dinar or to 0 in the Chilean peso).
If required, this value can be retrieved programmatically using the new i18n
getNumberOfCurrencyDigits
function.
In general, other convenient formatting functions also appeared, such as
formatDate
,
formatCurrency
,
formatPercent
and
formatNumber
.
It is quite convenient if you want to apply the same transformations that are done in the channels, but do it from the TypeScript code.
AnimationsIn Angular 6.0, animations are already possible without a
web-animations-js
polyfill, unless you are using
AnimationBuilder
. Your application can win several precious bytes! In case the browser does not support the
element.animate
API, Angular 6.0 rolls back to the use of CSS keyframes.
Angular ElementsAngular Elements is a project that allows wrapping Angular components in the form of web components and embedding them in an application that does not use Angular. At first, this project existed only in “Angular Laboratory” (that is, it is still experimental). With the release of v6, it comes to the fore a little bit and is officially included in the framework. This is a big topic that deserves a separate article.
ElementRef <T>If you want to take a link to an element in your template, you can use
@ViewChild
or
@ViewChildren
, or even embed an
ElementRef
directly. The disadvantage in this case is this: in Angular 5.0 or lower, the specified
ElementRef
will receive the type
any
for the
nativeElement
property.
In Angular 6.0, you can type ElementRef more strictly if you wish:
@ViewChild('loginInput') loginInput: ElementRef<HTMLInputElement>; ngAfterViewInit() {
What is considered undesirable, and what is fundamentally changingLet's talk about what you need to keep in mind when embarking on migration!
preserveWhitespaces
: default is false
In the “troubles that may occur during the update” section, we note that preserveWhitespaces is now
false
by default. This option appeared in Angular 4.4, and if you are wondering what to expect at the same time - here is
a whole post on this topic. Spoiler: everything can do, and can completely break your templates.
ngModel
and reactive formsPreviously, it was possible to provide the same form field with both
ngModel
and
formControl
, but today this practice is considered undesirable and will no longer be supported in Angular 7.0.
There is a little confusion, and the whole mechanism worked, perhaps, not as you expected (
ngModel
- this was not a familiar directive for you, but the input / output of the
formControl
directive, which performs almost the same but not identical task).
So now, if we apply the code:
<input [(ngModel)]="user.name" [formControl]="nameCtrl">
then we get a warning.
You can configure the application so that it gives a warning
always
(always),
once
(once) or
never
(never). The default is
always
.
imports: [ ReactiveFormsModule.withConfig({ warnOnNgModelWithFormControl: 'never' }); ]
Anyway, in preparation for the transition to Angular 7, you need to adapt the code to use either template-oriented forms or reactive forms.
Project Ivy: new (new) rendering engine in AngularSoooo ... This is the 4th major release of Angular (2, 4, 5, 6), and the rendering engine is being rewritten for the 3rd time!
Remember: Angular compiles your templates into equivalent TypeScript code. Then this TypeScript is compiled along with the TypeScript that you have written in JavaScript, and the result is available to the user. And we already have the 3rd version of this rendering engine in Angular (the first was in the original release of Angular 2.0, and the second was in Angular 4.0).
In this new version of the rendering engine, the approach to writing templates does not change, however, it optimizes a number of indicators, in particular:
All this is still deeply experimental, and the new Ivy rendering engine is enabled by the check box, which you yourself must enter in the compiler options (in the
tsconfig.json
file) if you want to try.
"angularCompilerOptions": { "enableIvy": true }
Consider that this mechanism is probably not very reliable, so do not use it in production yet. Perhaps he still does not earn. But in the near future, it will be accepted as the default option, so you should try it once, see if it works in your application, and what you gain from it.
Let's discuss in more detail how Ivy differs from the older rendering engine.
The code generated by the old engineConsider a small example: let us have a
PonyComponent
component that accepts a
PonyModel
model (with
name
and
color
parameters) as input and displays a pony image (depending on the suit), as well as a pony name.
Looks like that:
@Component({ selector: 'ns-pony', template: `<div> <ns-image [src]="getPonyImageUrl()"></ns-image> <div></div> </div>` }) export class PonyComponent { @Input() ponyModel: PonyModel; getPonyImageUrl() { return `images/${this.ponyModel.color}.png`; } }
The rendering engine, which appeared in Angular 4, generated for each template a class called
ngfactory
. The class usually contained (code simplified):
export function View_PonyComponent_0() { return viewDef(0, [ elementDef(0, 0, null, null, 4, "div"), elementDef(1, 0, null, null, 1, "ns-image", View_ImageComponent_0), directiveDef(2, 49152, null, 0, i2.ImageComponent, { src: [0, "src"] }), elementDef(3, 0, null, null, 1, "div"), elementDef(4, null, ["", ""]) ], function (check, view) { var component = view.component; var currVal_0 = component.getPonyImageUrl(); check(view, 2, 0, currVal_0); }, function (check, view) { var component = view.component; var currVal_1 = component.ponyModel.name; check(view, 4, 0, currVal_1); }); }
It is difficult to read, but the main parts of this code are described as:
- The structure of the created DOM, which contains the definitions of the elements (
figure
, img
, figcaption
), their attributes and the definitions of text nodes. Each element of the DOM structure in an array of view definitions is represented by its own index. - Change detection functions; the code they contain checks whether the expressions used in the template result in the same values ​​as before. Here the result of the
getPonyImageUrl
method is getPonyImageUrl
and, if it changes, the input value for the image component will be updated. The same applies to the nickname of a pony: if it changes, the text node containing this name will be updated.
Ivy generated codeIf we work with Angular 6, and the
enableIvy
flag
enableIvy
set to
true
, then in the same example a separate
ngfactory
will not be generated; information will be embedded directly into the static field of the component itself (simplified code):
export class PonyComponent { static ngComponentDef = defineComponent({ type: PonyComponent, selector: [['ns-pony']], factory: () => new PonyComponent(), template: (renderFlag, component) { if (renderFlag & RenderFlags.Create) { elementStart(0, 'figure'); elementStart(1, 'ns-image'); elementEnd(); elementStart(2, 'div'); text(3); elementEnd(); elementEnd(); } if (renderFlag & RenderFlags.Update) { property(1, 'src', component.getPonyImageUrl()); text(3, interpolate('', component.ponyModel.name, '')); } }, inputs: { ponyModel: 'ponyModel' }, directives: () => [ImageComponent]; });
Now everything is contained in this static field. The
template
attribute contains the equivalent of the usual
ngfactory
, with a slightly different structure. The
template
function, as before, will be launched for any change, but now it has two modes:
- Creation mode: the component is only created, it contains the static DOM nodes that need to be created
- The rest of the function is performed with each change (if necessary, updates the source of the image and the text node).
What does it change?Now all decorators are embedded directly in their classes (the same for
@Injectable
,
@Pipe
,
@Directive
), and to generate them you only need to know about the current decorator. In Angular, this phenomenon is called the “locality principle”: you do not need to re-analyze the application to recompile a component.
The generated code is slightly reduced, but more importantly, it is possible to eliminate a number of dependencies, thus speeding up recompilation if one of the parts of the application changes. In addition, with modern collectors, for example, Webpack, everything turns out much nicer: the non-functional code is reliably cut off, those parts of the framework that you do not use. For example, if you have no channels in the application, then the framework needed to interpret them is not even included in the final set.
We are used to the Angular code that gets heavy. It happens, it is not scary, but Hello World weighing 37 kb after minification and compression is too much. When Ivy is responsible for generating the code, the non-functional code is cut off much more efficiently. Now Hello World after minification is compressed to 7.3 kb, and after compression - just up to 2.7 kb, and this is a big difference. The TodoMVC application after compression is only 12.2 kb. This is data from the Angular team, and the others could not work with us, since in order for Ivy to work as described here, you still need to patch it manually.
If you are interested in details, watch this
performance with ng-conf.
Compatible with existing librariesYou might be interested: what will happen to libraries that are already published in the old format, if you use Ivy in your project? Do not worry: the engine will make an Ivy-compatible version of the dependencies of your project, even if they were compiled without Ivy. I won't tell you the interior right now, but all the details should be transparent.
New opportunitiesConsider what new features we will have when working with this display engine.
Private properties in templatesNew engine adds new feature or potential change.
This situation is directly related to the fact that the template function is embedded in the static field of the component: now we can use the private properties of our components in the templates. Previously, this was impossible, which is why we were forced to keep public all fields and component methods used in the template, and they fell into a separate class (
ngfactory
). When accessing a private property from another class, TypeScript compilation would fail. Now it is in the past: since the template function is in a static field, it opens access to the private properties of the component.
I saw a comment from the members of the Angular team about the fact that it is not recommended to use private properties in templates, although this is now possible - since it may be again prohibited in the future ... therefore, it is probably wiser to use only public fields in templates! In any case, writing unit tests is now easier, because the test can check the status of a component, even without generating or checking for this DOM.
i18n at runtimePay attention: the new rendering engine finally opens up a long-awaited opportunity for us and gives “i18n during execution”. At the time of this writing, she was still not quite ready, but we saw several commits at once, and this is a good sign!
The great thing is that you don’t have to pretty much change your application if you are already working with i18n. But now you no longer need to build the application again for each locale that you plan to support - it will be enough just to load JSON with translations for each locale, and Angular will take care of the rest!
Libraries with AoT codeAt the moment, a library released in NPM must publish the file metadata.json and cannot publish the AoT code of its components. This is sad because the costs associated with such an assembly are passed on to our application. With Ivy, there is no need for a metadata file, and library authors will now be able to publish their AoT code directly to NPM!
Improved StatisticsNow the generated code should give improved patterns, if you have a problem with your templates - result in a neat error indicating the template line in which it occurred. You can even set breakpoints in templates and track what is actually happening in Angular.
NgModule
disappear?This is still a distant prospect, but perhaps in the future it will be possible to do without NgModules. The first signs of such changes are tree-shakeable providers, and it is logical to assume that Ivy has all the necessary basic blocks for those who are ready to gradually abandon NgModules (or at least make them less successful). True, all this is still in perspective, we will be patient.
In this release there will not be many new features, but Ivy is definitely interesting for the future. Experiment with it - I wonder how you like it!