Friday, April 10, 2020

Exciting Times Ahead — Be Ready For Angular 9

Add Migration Support for Undecorated Classes#

Till Angular 8 decorators were optional for base classes for Directives and Components. This is applicable to Services that were not using @Injectable decorators.
export class BasePlain {
  constructor(private vcr: ViewContainerRef) {}
}

@Directive({selector: '[blah]'})
export class DerivedDir extends BasePlain {}
With Ivysuch classes require decorators as well, to take care of such cases once you migrate using ng update the decorators will be added as a part of the migration. Read this for more details, there are few cases that will not be taken care of.

FormControlName Accept Number as Input#

You may have used below code several times, but one thing we never thought of is how [formControlName]="i" works because it takes values of type string, this was fine there was no fullTemplateTypeCheck but in Ivy this will fail, to make sure the below syntax still works formControlName can accept value of type string | number.
<div formArrayName="tags">
  <div *ngFor="let tag of tagsArray.controls; index as i">
    <input [formControlName]="i">
  </div>
</div>

Replace TestBed.get with TestBed.inject#

In Angular 8 there was a breaking change where TestBed.get can no longer accept string value, the team decided to rollback the change as it was impacting a larger application base. Now that we have the type-safe version TestBed.injectTestBed.get is deprecated.
TestBed.get(ChangeDetectorRef) // returns any

TestBed.inject(ChangeDetectorRef) // returns ChangeDetectorRef

Default Value for Static Flag in ViewChild#

Another breaking change introduced in Angular 8 static flag required for ViewChild. The static property is still there but for using false value, we no longer need to pass this property. Once you update to Angular 9 using ng update the migration will remove wherever we are using { static: false }.
@ViewChild(ChildDirective) child: ChildDirective;
@ViewChild(ChildDirective, { static: false }) child: ChildDirective; // similar to above code

ng-add Support for @angular/localize#

To use @angular/localize we can now run ng add @angular/localize it will install the package and add necessary imports to polyfills, which is required to make it work.

FullTemplateTypeCheck for Template#

One issue which was always raised while working on Angular is “Why are templates not strictly type checked”. This was to some extent the case earlier, but now it will be strict for directives like *ngIf *ngFor and even pipes. There are 3 modes for type checking the templates:
  • Basic Mode: To enable set fullTemplateTypeCheck: false
  • Full Mode: To enable set fullTemplateTypeCheck: true
  • Strict Mode: To enable set fullTemplateTypeCheck: true and strictTemplates: true
Refer to this document for more details.

Typescript 3.6 support#

Typescript version 3.6 is required now. Here a gist from Lars Gyrup Brink Nielsen with Angular version and respective Typescript version support.
Angular CLI versionAngular versionNode.js versionTypeScript version
-2.x6.0.x or later minor version2.0.x
1.0.64.x6.9.x or later minor version2.2.x
1.1.34.x6.9.x or later minor version2.3.x
1.2.74.x6.9.x or later minor version2.3.x
1.3.24.2.x or later minor version6.9.x or later minor version2.4.x
1.4.104.2.x or later minor version6.9.x/8.9.x or later minor version2.4.x
(1.5.6)5.0.x6.9.x/8.9.x or later minor version2.4.x
1.5.65.1.x6.9.x/8.9.x or later minor version2.5.x
1.6.75.2.x or later minor version6.9.x/8.9.x or later minor version2.5.x
1.7.45.2.x or later minor version6.9.x/8.9.x or later minor version2.5.x
6.0.86.0.x8.9.x or later minor version2.7.x
6.1.56.1.x8.9.x or later minor version2.7.x
6.2.96.1.x8.9.x or later minor version2.9.x
7.0.77.0.x8.9.x/10.9.x or later minor version3.1.x
7.1.47.1.x8.9.x/10.9.x or later minor version3.1.x
7.2.47.2.x8.9.x/10.9.x or later minor version3.2.x
7.3.97.2.x8.9.x/10.9.x or later minor version3.2.x
8.0.68.0.x10.9.x or later minor version3.4.x
8.1.38.1.x10.9.x or later minor version3.4.x
8.2.28.2.x10.9.x or later minor version3.4.x
8.3.258.2.x10.9.x or later minor version3.5.x
9.0.79.0.710.13.x/12.11.x or later minor version3.6.x/3.7.x
9.1.x9.1.x10.13.x/12.11.x or later minor version3.6.x/3.7.x/3.8.x

Generic Support for ModuleWithProviders#

If you are an Angular library owner, there are high chances you may have used ModuleWithProviders with Angular 9. Now it is mandatory to use the generic ModuleWithProviders<T> type to indicate the Angular module type.
A migration schematic is already added, so ng update will take care of this migration.
Previous code:
@NgModule({ ...}) export class MyModule {
 static forRoot(config: SomeConfig): ModuleWithProviders {
   return {
         ngModule: SomeModule,
         providers: [{ provide: SomeConfig, useValue: config }]
   };
 }
}
After migration:
@NgModule({ ...})
export class MyModule {
  static forRoot(config: SomeConfig): ModuleWithProviders<SomeModule>
{
   return {
         ngModule: SomeModule,
         providers: [{ provide: SomeConfig, useValue: config }]
   };
 }
}

Apply Schematics to Libraries#

ng update takes care of all code migration, but it was not applied to Angular libraries. In Angular 9, ng update will apply all migration schematics to library projects as well. This is important if you are an Angular library author, so your code is in sync with the latest changes.

No more entryComponents#

If you have worked with a popup in Angular, you must have used this property. It was required so you can dynamically load the component without referring it in the template. With Ivy, this is not required anymore.

Breaking Changes

Removed tslib Dependency#

Angular does not depend on tslib now. In earlier versions of Angular, it was required and was part of the dependency. If you are not using Angular CLI you may need to install this package.

Forms#

  • ngForm: Earlier <ngForm></ngForm> was a valid selector, if you are using it move to <ng-form></ng-form>.
  • NgFromSelectorWarning: It was deprecated in Angular 6 and now removed the purpose of this directive was to display warnings when the deprecated ngForm selector is used.
  • FormsModule.withConfig: FormsModule.withConfig has been removed. we need to useFormsModule directly, withConfig used to take below options.
opts: { warnOnDeprecatedNgFormSelector?: "never" | "once" | "always"; }
  • The deprecated type RenderComponentType has been removed. Use RendererType2 instead.
  • The deprecated type RootRenderer has been removed. Use RendererFactory2 instead.

Angular Translation#

  • Translations (loaded via the loadTranslations() function) must now use MessageId for the translation key rather than the previous SourceMessage string.
  • To attach the $localize function to the global scope import from @angular/localize/init. Previously it was @angular/localize .
  • To access the loadTranslations() and clearTranslations() functions, import from @angular/localize. Previously it was @angular/localize/run_time.

Service Worker#

versionedFiles property is removed in ngsw-config.json
Before
"assetGroups": [
  {
    "name": "test",
    "resources": {
      "versionedFiles": [
        "/**/*.txt"
      ]
    }
  }
]
After
"assetGroups": [
  {
    "name": "test",
    "resources": {
      "files": [
        "/**/*.txt"
      ]
    }
  }
]

Angular Bazel#

  • @angular/bazel ng_setup_workspace() is no longer needed and has been removed. Angular will assume you will fetch rules_nodejs in your WORKSPACE file, and no other dependencies remain here. Simply remove any calls to this function and the corresponding load statement.
  • If you are using protractor_web_test_suite from @angular/bazel now switch to the @bazel/protractor package.

Deprecations

  • TestBed.get function is now deprecated in favor of type-safe TestBed.inject.
For a complete guide refer to Official docs. Also, nothing is covered related to Ivy as it is a really big topic and we will be writing a blog post soon to cover all features of Ivy.

Angular CLI

Support to verify CLI Version#

Check is added to verify if the installed CLI version is latest published version If it is not while running ng update it will install the latest version as a temporary package to run the migration.

Support to Mix Multiple Configuration#

Earlier with ng build we can pass configuration by using --configuration the one problem is if I want to override some configuration, we have to copy the entire configuration and make a new entry to use it.
Now it is possible to use ng build --configuration=prod,testing so in testing configuration we can only pass the configuration which needs to be overwritten.

Specify options for ng-add#

Another update if you are an Author for Angular Library, with ng add you can specify if the package should be added to dependencies or not.
You can specify below option in package.json
ng-add : { 
     "save": false | true | 'dependencies' | 'devDependencies'
}

Type options for component schematic#

As of now, when we use ng g c user it will generate a file with the component class UserComponent the type option lets you define which type of component you are creating for example ng g c user --type="dialog" it will create a component with the class name UserDialog .

Schematics Support to Generate Interceptor#

Adding an interceptor was manual till now, with Angular 9 we will be able to use ng g i custom to create a CustomInterceptor class.

Change to app-shell schematic#

To generate app-shell we had to pass --clientProject it will be optional now, it will consider the default project instead, if not provided.

Skip Test While using Generate Schematics#

If we create an application with --minimal=true it skips the e2e and unit testing configuration. But when we use ng g to generate a component/pipe/service it adds a spec.ts file. Starting from Angular CLI 9, this will be taken care of.

Autodiscover multiSelect schema prompt#

To create a prompt that can have multiSelect we have to provide a lot of other options, with Angular 9 it can be simplified like below configuration.
test: {
  type: 'array',
  'x-prompt': {
    'type': 'list',
    'multiselect': false,
    'items': [
      {
        'value': 'one',
        'label': 'one'
      },
      {
        'value': 'two',
        'label': 'two'
      },
    ],
    'message': 'test-message',
  },
}

Support to provide npmrc file path#

NPM_CONFIG_USERCONFIG and NPM_CONFIG_GLOBALCONFIG variables are available in npm and when provided Angular CLI will prefer them over the global .npmrc file. Read npm docs for more detail.

Breaking Change#

  • styleext and spec options are removed while using CLI , use style and skipTests options instead.

Angular Component

New Clipboard Module#

A new clipboard component is available which is part of the @angular/cdk family.
If you want to read more on how to implement do read below blog post from Tim Deschryver.

hammerjs is now optional#

In earlier versions hammerjs was required to add gesture support, it is optional now and all implementation used internally is removed, you can use HammerModule from @angular/platform-browser.

New Package for Google Maps#

@angular/google-maps package is available now, integrating google maps was always a difficult task not anymore, this package is already tested on multiple devices. You can refer to the blog post from Tim Deschryver on how to implement it.

Google Maps is now an Angular component#

The new Angular Component release introduces the second official @angular/component component, a Google Maps component.

Breaking Changes#

  • Components can no longer be imported through @angular/material. Use the individual secondary entry-points, such as @angular/material/button.
  • MAT_CHECKBOX_CLICK_ACTIONis deprecated, use MAT_CHECKBOX_DEFAULT_OPTIONS

Combining AngularJS with existing Components

A common question when looking at AngularJS for the first time is about how to combine it with existing JavaScript code and components. For jQuery UI in particular, the team around AngularJS has created the AngularUI project (http://angular-ui.github.com/) which provides an easy way to use jQuery UI with Angular.). For other frameworks - or especially for your custom DOM-manipulating code - the necessary steps might not be as obvious at the first glance.
As you have seen in the previous chapters, AngularJS puts a very strong focus on separation of concerns:
  • Controllers expose model objects and methods to a view (via the use of $scope). They contain view-specific code without any DOM manipulations.
  • Services contain methods which perform view-independent operation. Business-logic code which is used by multiple views and/or in different contexts is right at home here. (Note: some global services might even operate on the DOM level -- for example to show modal dialog boxes or similar. These manipulations should however be independent from of concrete view in your application.)
  • Directives are re-usable components which bind model values to DOM properties and react on DOM events to update model values
Most existing components (especially in the jQuery-world) usually work in a different way. Without Angular, when you wanted to achieve a certain behavior, like displaying a date-picker, you would usually go with a jQuery UI extension (like 'datepicker') and use call similar to $("#someinput").datepicker({...}). This would extend a standard <input> element with an ID of someinput to be turned into a datepicker.
In this command's options, you would have usually specified a callback to be invoked whenever the user selects/changes the date in the input box. But you wouldn't do this just for a single datepicker -- no, this kind of jQuery UI-extension would be littered throughout your business code for nearly every input control in your HTML. After all, your datepickers need access to the underlying model values to restrict the input to valid ranges and to perform additional validation if necessary. This mix of business code and DOM-manipulating code is sometimes the reason for maintenance headaches of JavaScript developers.
With this in mind, you can see the conundrum: how do you take classic JavaScript code (with this mix of DOM interactions and event handlers with direct manipulation of underlying business objects) and put it into the well defined structure of AngularJS?
So let's just see how we're going to take a jQuery-based fragment and move it forward to a reusable AngularJS directive. In the following example, you'll see a simple text entry box which is treated as a jQuery UI datepicker. When changing the date, the underlying business object will be updated and the value will be shown underneath the input box.
This demonstration consists mainly of two parts. The first is the rather straightforward and easy to understand HTML markup:
<div>
    Date Of Birth:
    <input type="text" id="dateOfBirth">
    <br>
    Current user's date of birth:
    <span id="dateOfBirthDisplay"></span>
</div>
So far, so good. Now let's have a look at the jQuery-based code behind this HTML:
$(function () {
   var user = {
      dateOfBirth: new Date(1970, 0, 1)
   };

   var displayValue = function () {
      $("#dateOfBirthDisplay").text(new Date(user.dateOfBirth).toDateString());
   };

   var processChange = function() {
      user.dateOfBirth = $("#dateOfBirth").datepicker("getDate");
      displayValue();
   };

   $("#dateOfBirth").datepicker({
         inline: true,
         onClose: processChange,
         onSelect: processChange
      }
   );

   displayValue();

   // initial display of value in input-box
   $("#dateOfBirth").datepicker("setDate", user.dateOfBirth);

});
Please note that this fragment, even though it already mixes code from different areas of responsibility (DOM manipulation, data conversion, business object population) is hardly complete: in practice, you'd quite likely also have to add validation code to check for date formats and ranges. But let's keep this simple for now.
Now, if code like this would only occur once throughout your application, its use could be quite acceptable. Unfortunately, without a framework like AngularJS, code like this would be written in multiple throughout your application. In fact, whenever you'd have a date-entry box, you would quite likely see code like this. As you can imagine: maintenance of a codebase like this might be quite a bit harder than it should be.
As you've seen before, AngularJS allows you to separate the concerns if your code into three different parts: the model, the controller and directives which perform DOM manipulation. So let's look at this particular example and how we can transform this into a more suitable - and maintainable - form.

Separating the Concerns

At first, we will change the HTML markup to tell AngularJS which internal application name should be used (as we will register the directives with this application). In addition, we'll already add the necessary data-binding information and add the directive my-datepicker instead of the <input>-element which we've used before. (Please note: this particular directive does not yet exist in AngularJS, but we'll build it throughout the remainder of this chapter.)
<div ng-app="demo" ng-controller="DemoController">
    Date Of Birth:
    <my-datepicker type="text" ng-model="user.dateOfBirth" />
    <br>
    Current user's date of birth:
    <span id="dateOfBirthDisplay">{{user.dateOfBirth}}</span>
</div>
The matching controller simply exposes the user property via its $scope and we have removed all interactions with jQuery UI from this business logic code:
function DemoController($scope) {
   $scope.user = {
      dateOfBirth: new Date(1970, 0, 1)
   }
}

Creating the Directive

To create the link to jQuery UI's datepicker-addin, we are introducing the following directive. (Worry not: we'll discuss this directive line-by-line just in a few seconds.)
angular.module("demo", []).directive('myDatepicker', function ($parse) {
   return {
      restrict: "E",
      replace: true,
      transclude: false,
      compile: function (element, attrs) {
         var modelAccessor = $parse(attrs.ngModel);

         var html = "<input type='text' id='" + attrs.id + "' >" +
            "</input>";

         var newElem = $(html);
         element.replaceWith(newElem);

         return function (scope, element, attrs, controller) {

            var processChange = function () {
               var date = new Date(element.datepicker("getDate"));

               scope.$apply(function (scope) {
                  // Change bound variable
                  modelAccessor.assign(scope, date);
               });
            };

            element.datepicker({
               inline: true,
               onClose: processChange,
               onSelect: processChange
            });

            scope.$watch(modelAccessor, function (val) {
               var date = new Date(val);
               element.datepicker("setDate", date);
            });

         };

      }
   };
});
Contrary to the example which you've seen in Introduction to Directives, we're not simply returning a link function. Instead, we're returning a so called compile function for this directive.

The Compile Function

The reason for this is based on the internal logic in which directives are applied by Angular: in the first phase, the compile phase, you can modify the HTML-element which will be added to the DOM at the location of your directive. You can for example emit a completely different HTML element. In the second phase, during linking, you can change the content and behavior of the element after the result of the compilation has been added to the DOM.
The important thing to note is that, if your directive uses a compile function, it is required to return the link function which should be called at a later time.
So let's look at the individual elements of our directive, step by step.
First, we're defining a module demo (which we will later reference from the HTML) and we're adding a directive called myDatepicker to it. We require a reference to $parse to be injected in the code so that we can later use this to parse model-binding expressions.
angular.module("demo", [])
.directive('myDatepicker', function ($parse) {
We then indicate that our directive will be used as an HTML-elements (and not as an attribute or CSS-class).
restrict: "E"
We then tell AngularJS that we want to replace the element with the result of our directive but don't require automatic transclusion of the content. (You can read more about transclusion at Introduction to Directives).
  replace: true,
  transclude: false,
The remainder of the directive is the compile function. Let's first look at the compilation inside this function. At the beginning of this method, we use $parse to parse the ng-model attribute which has been specified in the HTML markup (it contains the target field for data binding).
$parse converts (or compiles) an AngularJS expression into a function. If we for example specify "user.dateOfBirth" as an expression, then $parse will return a function which allows us to retrieve and set this value from the underlying scope. (Please note: as always with AngularJS, the naming convention is lowercase-with-dashes in HTML and camelCase in JS, so that the JavaScript field attrs.ngModel will contain the string-contents of the HTML-attribute ng-model).
compile: function (element, attrs) {
    var modelAccessor = $parse(attrs.ngModel);
After this, we use jQueryOnly (or the AngularJS-supplied minimum jQuery equivalent) to create an <input>-element and then replace the existing directive element (which temporarily exists in the HTML) with this new <input>. (And while doing this, we're preserving the HTML element's ID from the HTML element which defined this directive.)
    var html = "<input type='text' id='" + attrs.id + "' >" +
                "</input>";
     var newElem = $(html);
     element.replaceWith(newElem);

The Link Function

Up to this point, we've looked only at the compilation part. What's next is that we're returning the link function from this compile function:
    return function (scope, element, attrs, controller) {
            /* ... */
    };
In this returned link-function, we're doing three things:
  • define a function which will be called as the datepicker's onClose and onSelect callbacks.
  • use jQuery UI's datepicker() extension to add the desired behavior to the underyling <input>-element
  • watch the model for changes to update the datepicker's display whenever the model changes
Let's first have a look at the function which will later be used for the datepicker's callbacks.
        var processChange = function () {
           var date = new Date(element.datepicker("getDate"));
           scope.$apply(function (scope) {
              modelAccessor.assign(scope, date);
           });
        };
The important parts here happen in the call to scope.$apply. This method is used to change model values in a way which allows AngularJS' change tracking to update the underlying HTML elements. (Or more correctly, it triggers the watches which have been registered for the model).
The modelAccessor we're using in this method is the return value of the earlier call to $parse(...): its assign method is a preprocessed accessor function which sets the configured (according to the ng-model attribute on the HTML directive) field of the corresponding object to the specified value.
To recap: whenever this processChange-function is called, it will extract the current value from the HTML element which underpins the datepicker by using its getDate access-method. It will then apply the changes to the underlying model by using the assign() method of a pre-parsed accessor within an $apply-block.
In the next step, we simply use jQuery UI's datepicker extension to get the desired client-side behavior. We pass the processChange- function which we've defined above as the callbacks for onClose and onSelect. In this way processChanges will be called whenever the user changes the datepicker's value.
        element.datepicker({
           inline: true,
           onClose: processChange,
           onSelect: processChange
        });
And as the final step, we watch the model for changes so that we can update the UI as required. To do this, we pass the modelAccessor (which we've received as the return value from $parse) to Angular's $watch method, which is part of the current scope.
The callback to $watch will be triggered whenever the model value has been changed. (By using $apply or by updating a bound UI control).
        scope.$watch(modelAccessor, function (val) {
           var date = new Date(val);
           element.datepicker("setDate", date);
        });

The HTML

In this way, we have now defined a re-usable directive which works independent from any GUI. In fact, this component could easily be re-used in a completely different AngularJS application.
If you run this initial version of the AngularJS application, you can see the following behavior:
At this point, the data-binding works as expected, we have a very clear separation of concerns (there is absolutely no direct binding between your business code in the Controller and jQuery UI's datepicker.)
This was a preview-quality chapter of our continuously deployed eBook on AngularJS for .NET developers. If you enjoyed this chapter, you can read more about the project at http://henriquat.re. You can also follow us on twitter (Project: @henriquatreJS, Authors: @ingorammer and @christianweyer)