6

Angular 17: The Web Framework's Newest Release

 9 months ago
source link: https://devm.io/angular/angular-17-newest-release-001
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Renaissance? Angular 17! - All features at a glance

Angular 17: The Web Framework's Newest Release

08. Nov 2023


Version 17 of Angular, the popular JavaScript framework, is arriving. There’s a lot going on with Angular. Some people can lose track of things, or even become scared of getting overwhelmed. This article examines if this concern is justified or if the Angular team’s plan to usher in an Angular renaissance can succeed.

First of all: Anyone who has been putting off Angular upgrades or is only planning to update at the end of an LTS cycle should closely watch current developments and evaluate if incremental updates in small steps instead of a large leaps makes more sense in terms of effort and risk.

Of course, we’ll take a look at the Angular Core changes as well as TypeScript and Angular Material.

TypeScript

The new Angular version also supports a new version of TypeScript. In Angular 17, TypeScript version 5.2 or higher must be used. Since Angular 16 still required TypeScript 5.0, let’s take a look at some interesting new features from both TypeScript 5.1 and TypeScript 5.2.

Implicit “return“ when “undefined“ is returned

If a JavaScript function doesn’t contain a "return" statement, it still always implicitly has "undefined" as its return value. This fact wasn’t reflected in TypeScript yet. If "undefined" was specified as the return type for a function, a "return" statement always had to be used explicitly too. Additionally, a function that explicitly returned "undefined" wasn’t compatible with a "void" function. See Listing 1.


declare function onEvent(f: () => undefined): undefined;

  

onEvent(function f() { })

// Argument of type '() => void' is not assignable to parameter of type '() => undefined'.

Listing 1: “undefined” and “void” were previously incompatible in TypeScript

This is now improved in TypeScript 5.1, so the above example no longer throws compiler errors. A function with "undefined" as the return type also no longer requires an explicit "return", similar to the "demo1()" function in Listing 2. Alternatively, an explicit but "empty" return can also be used, as in the "demo2()" function.


function demo1(): undefined {

// no returns

}

function demo2(): undefined {

return;

}

Listing 2: No more return needed for the “undefined” return type

Independent getter and setter types

Previously, TypeScript get and set functions that belonged together (for example, if they had the same name) had to have compatible types. For a setter that can be given the type "string|number|boolean", previously you could only specify a subtype like "string" as the corresponding getter. In TypeScript 5.1, these types can be assigned completely independently, but they must be assigned explicitly. See Listing 3.


interface MyElementStyling {

set style(newValue: string);

get style(): CSSStyleDeclaration;

}

Listing 3: Independent getter and setter types.

Decorator Metadata

In TypeScript, a decorator is a function that is given a class, property, or method in order to extend the passed entity’s behavior. TypeScript 5.2 adds an exciting feature specifically for the Angular framework: the ability to add metadata to a decorator. This feature was already in the "Experimental Decorators" originally used by Angular, but now it’s also in the stable TypeScript specification. For example, in Listing 4, the decorator "setMetadata" is defined. A decorator is a function that’s given the decorated entity as a "target" and the associated "context". The "metadata" property was added to this context object in TypeScript 5.2. The metadata can be understood as a key-value store. Its corresponding TypeScript type is called "record". The metadata key is the name of the decorated entity; in Listing 4, for example, the property "demoAmount" is decorated. Therefore, it becomes a metadata key. Listing 4 shows how metadata can be written by accessing "context.metadata". The decorated entity’s name is in "context.name". In Listing 4, the string "Demo data" is stored in the metadata for each property decorated with "@setMetadata".

Stored metadata can be read from the class with the TypeScript metadata symbol ("Symbol.metadata").


interface Context {

name: string;

metadata: Record<PropertyKey, unknown>;

}

function setMetadata(_target: any, context: Context) {

context.metadata[context.name] = 'Demo-Data';

}

class MyClass {

@setMetadata

demoAmount = 123;

@setMetadata

demoAction() { }

}

  

const ourMetadata = MyClass[Symbol.metadata];

console.log(JSON.stringify(ourMetadata));

// { "demoAmount": "Demo-Data", "demoAction": "Demo-Data" }

Listing 4: Set and read metadata in TypeScript

To better configure a decorator, you can implement a decorator factory. It will then return the actual decorator. This is shown in Listing 5 using the "DemoMeta" decorator factory, which receives the metadata key to be set and the associated metadata value as parameters and returns the configured decorator. The "DemoMeta" factory is used in Listing 5 to add the metadata entry "'entity'" with the value "'3270'" to the "DemoClass" class. The metadata entry "'demo'" with the value "'42'" is added to the "demoMethod()" method. Then, the class metadata is accessed again with "Symbol.metadata".


function DemoMeta(key: string, value: string) {

return (_: any, context: Context) => {

context.metadata[key] = value;

};

}

  

@DemoMeta('entity', '3270')

class DemoClass {

@DemoMeta('demo', '42')

demoMethod() {}

}

  

DemoClass[Symbol.metadata].entity; // '3270'

DemoClass[Symbol.metadata].demo; // '42'

Listing 5: Example Decorator Factory in TypeScript

After our look at TypeScript, next we will turn to the Angular CLI. This is typically used to build Angular applications.

Angular CLI

The Angular team provides the Angular CLI for developers to manage Angular projects. An important step in Angular CLI 17 is the Angular build system update. In Angular CLI 17, newly created projects are now built with the new build system based on "esbuild" as the standard for the first time. This is primarily meant to speed up the build and optimize the build result or make it smaller.

The new name "Vite" (pronounced like the French. "vite" = "fast") based live development server promises a better, faster development cycle. For example, Vite lets you apply changes to the global CSS without needing a live reload.

For apps to be built entirely with the esbuild-based build system in the future, the esbuild builder needed some enhancements. For example, basic support for checking Angular CLI bundle budgets was added to help developers keep track of the size of JavaScript and CSS files generated in the build. Basic support for building WebWorkers has also been added. These are a browser-native feature, but still must be considered in the Angular build, since worker scripts are typically written with TypeScript. The old builder was already able to do this, and now the new esbuild builder follows suit.

Since they’re built in Angular, SPAs have one disadvantage. Once the initial web page has loaded, some scripts must first be loaded, which start the application. Depending on the internet connection and the page size, this takes a moment. Along with the fact that scripts have to be executed in order to display the content of the page, this is bad for the page’s SEO ranking.

Previously, the "@nguniversal" package could be used to make an Angular application SEO-fit. "@nguniversal" can (pre-)render an Angular application on the server side. The page content is visible immediately after the HTML has been loaded, since it no longer has to be generated dynamically in the client. This leads to a fast page load, better usability, and a better SEO ranking. Previously, the pre-rendering packages were in a separate namespace "@nguniversal", although these packages were also maintained by the Angular team. In Angular 17, most of the code has been moved from "@nguniversal" to the "@angular/ssr" package in the "@angular" namespace. However, apart from renaming, the functionality is essentially the same.

The topic of SSR (Server Side (pre-) Rendering) has become more important to the Angular team, as can be seen from the above change and the fact that it has become easier to integrate packages. When creating a new Angular 17 project, you’ll be asked if server-side rendering should be activated. Alternatively, the new option "ng new --ssr" can be used to generate a project with preconfigured SSR. In this project, Angular 16’s hydration is also directly activated. This will ensure a seamless transition from the server-side generated page to the fully functional Angular app without flickering.

Breaking Changes in Angular CLI 17

Angular 17 keeps its own dependencies up to date. At least TypeScript version 5.2 and zone.js version 0.14.0 must now be used for Angular and Angular CLI. The minimum required node.js version is upgraded to 18.13.0.

Some defaults have also been replaced in Angular CLI. For example, when a new application is generated, the Angular router is now initialized by default. This wasn’t previously the case. If no routing is needed, the command line option "--no-routing" must be specified.

As of Angular CLI 17, all applications are generated as "standalone" applications by default, such as an application without "@NgModule()".

Another changed default value concerns the Angular interceptors. Angular HTTP interceptors can be generated with the "ng g interceptor" command. Previously, class-based interceptors were generated with this command. In Angular CLI 17, functional interceptors are generated instead. If a class-based interceptor is generated, the "--no-functional" option must be added to the command.

angular17_0

Figure 1: Old Angular-Logo on the left and new Angular-Logo on the right.

Angular

Angular 17 comes with a lot of new features. The Angular team calls it one of the "biggest" releases in its history. Besides the version updates for zone.js and TypeScript, it also offers many smaller and larger innovations. Two innovations related to the new APIs regarding Signals have already been added in Angular 16.2.0. Namely, it adds two new component lifecycle hooks. These are special because they don’t have to be implemented with an interface like conventional lifecycle hooks. They can be called as a callback function. This is similar to the "computed()" function in Signals. Listing 6 shows an example with the new hooks. The two hooks are called "afterRender()" and "afterNextRender()" and are executed, according to their name, after Angular renders the respective component. The difference between the two hooks is that "afterNextRender()" is only called once, after the next rendering process, while "afterRender()" remains active as long as the component is active. However, these two hooks are still listed as "Developer Preview" and are subject to change.

@Component({})

export class DemoComponent {

val = 42;

constructor() {

afterRender(() => {

console.log('afterRender', this.val);

});

afterNextRender(() => {

console.log('afterNextRender', this.val);

});

}

}

Listing 6: Implementing the new lifecycle hooks

Signals

An important detail in Angular 17 is that the signals introduced in Angular 16 are now stable. The goal of introducing Signals was to make zone.js optional by introducing a new, signal-based change detection mechanism. In Angular 17, the team has come a step closer to this goal. Signals enable "local change detection", a change detection process that only refers to the components that have changed (signal) values. Figure 1 shows an example of the difference between the change detection mechanisms. In the standard case, the complete component graph is checked for each asynchronous event. With OnPush-ChangeDetection, components that triggered the change detection or have changed "@Input()" values and their respective parent components (up to the AppComponent) are checked. With Signals, you can now check only the components that have changed signal values.

Comparing change detection mechanisms

Figure 2: Comparing change detection mechanisms

Animations

Animations can be reloaded in Angular 17 with lazy loading. The browser must first load smaller bundles, making the initial application smaller, leading to shorter application loading and start times. To activate lazy loading for animations, the "provideAnimationsAsync()" function must be used. Here, the "BrowserAnimationModule" should no longer be imported and the "provideAnimations()" function should no longer be used.

The special thing about an SPA is that, as a rule, an HTML document is only loaded when the application starts. When switching to a different route, only components in the browser DOM are exchanged. Animations cannot simply be executed on page load, as is the case with server-side applications. Instead, animations must be better adapted to SPA logic, such as route changes. This is what the new View Transitions DOM API is designed for. This API provides the callback "document.startViewTransition()", which tells the browser that a view transition will start. Logic can be passed to the function that defines how the browser DOM should look at the end of the transition. The transition animation can be created using special CSS pseudo selectors. Angular uses this new API in the Angular 17 router so that a view transition is started for each navigation. However, since the "View Transition" feature is still experimental and has only been implemented in Chrome and Edge, router navigation in browsers such as Firefox or Safari continues to take place synchronously. To activate ViewTransitions, the "withViewTransitions()" function can be added to the router at startup. In Listing 7, the "withViewTransitions()" is also given a configuration object in which an "onViewTransitionCreated()" callback can be defined if additional (animation) logic is needed.


provideRouter(

routes,

withViewTransitions({

onViewTransitionCreated: transitionInfo => {

}

})

),

Listing 7: Activating the ViewTransitions in the AppModule or app.config

New Control Flow Syntax

The biggest and most far-reaching innovation for developers, and the biggest innovation in Angular in terms of template syntax, is the new control flow syntax. In Angular 17, this syntax is still in a developer preview. But in the future, it will completely replace the previous directives "*ngIf", "*ngFor" and "ngSwitch". The logic of these directives will become part of the framework itself, instead of "normal" directives. This will allow the Angular team to better optimize the respective logic. Signals and the goal of making zone.js optional with the help of Signals were also a key reason for these innovations, since the old control flow directives were based on and needed "zone.js".

Besides the control flow logic, another feature that can be implemented thanks to the new syntax is a new form of lazy loading at component level. This feature, also known as "deferrable views", is discussed in more detail below.

First, let's familiarize ourselves with the basic syntax. Until now, "control flow" in Angular templates was handled by structural directives like "*ngIf" and "*ngFor". See Listing 8. In the future, structural directives can still be used and built, but "*ngIf", "*ngFor" and "ngSwitch" will be implemented with the new syntax. The example in Listing 8 can be easily rewritten in the new syntax, as shown in Listing 9. You’ll see that the "if" is no longer an HTML attribute, but an independent syntax construct marked with an "@". The condition is specified in round brackets, just like in TypeScript. This is followed by the template to be displayed in single curly brackets.


<div><span *ngIf="show">Content here</span></div>

Listing 8: Previous “Control Flow” with Angular Directives


<div>@if (show) { <span>Content here</span> }</div>

Listing 9: “@if” with the new Control Flow syntax in Angular 17

Incidentally, the new syntax has changed slightly since the first RFC. In the original proposal, the example in Listing 9 would look a little more complex: (“{#if show} ... {/if}” statt “@if (show) { ... }”).

The new syntax has a clear advantage over the old structural directives, especially for "if-elseif-else" conditions. Previously, separate templates had to be created in an "" element for the "else" case. "if-elseif-else" constructs weren’t possible at all. "if-else" constructs had to be nested instead. Listing 10 shows the new syntax, where both are easily possible. The syntax is reminiscent of JavaScript. It’s important here that the keywords "@if" and "@else" are always preceded by an "@".


@if (show) {

<div>case content 1</div>

} @else if (otherCond) {

<div>case content 2</div>

} @else {

<div>other content</div>

}

Listing 10: “@if-@else” with Angular 17 Control Flow syntax in the template

The "*ngFor" directive will also be replaced with the new control flow syntax "@for". Listing 11 shows the corresponding syntax. You will immediately see that in the "for" statement, "item of items" is now written. It is no longer "let item of items", as was needed with "*ngFor". You must now specify a "track" option that takes over the role of the "trackBy" option from "*ngFor". There is an important difference here. The new "@for-track" option isn’t optional, it must always be specified. It’s used internally by Angular to optimize rendering the list so that the list does not have to be completely re-rendered all the time, especially if just individual list entries are changed. A completely new algorithm for list rendering was made possible by the new "@for" syntax. It also helps here and shows significant performance improvement in benchmarks compared to the old algorithm. The value specified in the "track" expression must identify the respective collection entry. If an array of objects is iterated over, the respective object ID should be used, as seen in Listing 11. If you are iterating over an array of primitive values, you can simply use the value itself ("track item").

An innovation that we did not know from "*ngFor" until now is "@for" can be extended by "@empty{}". The template code in the "@empty" expression is always displayed if the transferred list (here "items") has no entries or is "null" or "undefined".

Several implicit variables can also be used within the "@for" expression to display the current loop iteration index (via "$index"), for example. See Listing 11. You can also access the loop length ("$count") to check if the current loop index is even ("$even") or odd ("$odd"), or if it is the first ("$first") or last ("$last") loop element.


@for (item of items; track item.id) {

<p>Element Nummer {{$index}}: {{item.name}}</p>

} @empty {

<p>No items</p>

}

Listing 11: Example of a loop with the new “@for” Syntax in Angular 17

Previously, three directives were used to implement a switch case statement: "ngSwitch" and the structural directives "*ngSwitchCase" and "*ngSwitchDefault". With the new syntax, "@switch", "@case" and "@default" can now be used, as seen in Listing 12. Of course, you can still switch via primitive values or enum values, for instance. Just like the other control flow statements, normal Angular template code can be used as the cases content. You can also nest the control flow statements.


@switch (value) {

@case (1) {

<div>case one</div>

}

@case (2) {

<p>case two</p>

}

@default {

<span>Default-case</span>

}

}

Listing 12: Example of a switch case with the new “@switch” syntax in Angular 17

The last element of the new control flow syntax offers new syntax and completely new functionality. "Deferred Views" (with "@defer") enable lazy loading at the component level without needing the router or complex renderer constructs. An example of a deferred view can be seen in Listing 13. Here, the component "" is the component that will be reloaded. The "when" expression is optional. If it’s omitted, lazy loading begins once the outer component is rendered. Otherwise, the "when" expression can be used to load the "@defer" block’s content only after a certain condition occurs. In the simplest case, a Boolean expression ("isLoaded") can be queried, as seen in Listing 13.

In addition to the "@defer" block, Listing 13 shows more optional blocks. A template can be specified in "@loading" to represent the "loading status", like a loading spinner. The loading status is displayed the moment the Boolean condition (here "isLoading") changes to "true". When the "" component is loaded and rendered, the loading status is automatically removed. Developers should be especially careful with the loading state and avoid UX pitfalls. One large spinner for the entire page is easier for a user to “digest” than 20 simultaneously active spinners spread across the entire page and all individual components. When using load states, take care to make sure the page doesn’t permanently "flicker" due to piecemeal reloading the smallest page elements or that the content doesn’t constantly shift back and forth.

Here, "@placeholder" content is displayed until the "isLoaded" condition first has the value "false". One important note here: "@defer" doesn’t work like "@if" in the sense that you can switch back and forth between the values. Once "isLoaded" has the value "true", the lazy loaded component is displayed. Even if "isLoaded" is manually reset to "false", the "@defer" content continues to be displayed and doesn’t switch back to the "@placeholder" content.

If an error occurs when reloading the "@defer" content, (due to a network interruption, for example), content from the "@error" block is displayed.


@defer (when isLoaded) {

<app-main-content/>

} @loading {

<p>Loading...</p>

} @placeholder {

<icon>pending</icon>

} @error {

Failed to load

}

Listing 13: Example of the new “@defer” expression in Angular 17

Besides the "when" specification with a Boolean expression, you can also specify an "on" condition. Listing 14 shows an example of the "on immediate" and the "on idle" condition. Here, "on immediate" behaves as if the expression were omitted. Once the outer component is rendered, components in the defer block are reloaded. With "on idle", the components are loaded as soon as the browser is in the "idle" state (technically, this corresponds to the browser’s "requestIdleCallback()" function).


@defer (on immediate) {

//...

@defer (on idle) {

//...

Listing 14: Examples of "on" expressions that load directly or only in the idle state

In addition to these fairly static specifications, you can also react to dynamic user interactions with the "on interaction" conditions. To react to an interaction, an element that the user should interact with must be specified. The example in Listing 15 shows that either an external element like a button, can be transferred to the "interaction" condition using a template reference variable. Alternatively, this explicit assignment can be omitted. But then a "@placeholder" must be specified, since the placeholder content serves as the interaction element. This is shown in the middle of Listing 15.

This could be used to reload a detailed view at the push of a button, which contains complex parsing logic (or parsing and displaying XML) in the form of components and services that should only reload "on demand".

The "on hover" condition works similarly to the "on interaction" specification. See Listing 15. The only difference is that instead of an interaction, you only need to hover over the trigger element.


@defer (on interaction(trigger)) {

<app-my-content/>

}

<button #trigger>Trigger</button>

  

@defer (on interaction) {

<app-my-content/>

} @placeholder {

<button>Trigger</button>

}

  

@defer (on hover(trigger)) {

<app-my-display/>

}

<button #trigger>Trigger</button>

Listing 15: Options for loading defer content based on an interaction trigger

Instead of direct user interaction, you can also load a defer block when another (trigger) element comes into the visible viewport. for instance, when the user scrolls to the trigger element. See Listing 16. Instead of the external trigger element, the placeholder can also be used as an implicit trigger element.


@defer (on viewport(trigger)) {

<app-my-content/>

}

<div #trigger style="margin-top: 1500px">Content</div>

Listing 16: Viewport triggers are used to load content when the trigger element is visible (in the viewport)

Last but not least, a defer block can also be reloaded using a time trigger. You must give a trigger a duration to load the component after. For example, Listing 17 specifies that the defer block should start loading the "" component after 1.5 seconds.

Regardless of the "on" condition used, the "@loading" block can be given an "after" and/or a "minimum" duration. In Listing 17, the "after" specification ensures that the loading state is displayed after 100 milliseconds at the earliest. The "minimum" specification makes sure that the loading state is displayed for at least 150 milliseconds. Similarly, the "minimum" specification for "@placeholder" ensures that the placeholder is displayed for at least 150 milliseconds before displaying the loading state.


@defer (on timer(1500ms)) {

<app-my-content/>

}

@loading (after 100ms; minimum 150ms) {

<p>...Loading...</p>

}

@placeholder (minimum 100ms) {

Placeholder

}

Listing 17: Timer condition and minimum specifications

The new defer syntax also allows direct optimization. An optional "prefetch" statement can also be specified in the defer condition. The logic specified after the prefetch statement corresponds to the defer logic described above. In Listing 18, a component should only be displayed when the state of the variable "isVisible" changes to "true". The prefetch condition is used so the component can be displayed now without loading time. Once the "prefetchCondition" variable changes to "true", Angular prefetches the "@defer" block content. If "isVisible" changes to "true", no more loading time is needed, since the component is already "present" and only needs to be rendered. In the example shown in Listing 18, an "on" condition is used for pre-fetching instead of a "when" condition. In this case, Angular starts reloading the component when the outer component has rendered.


@defer (when isVisible; prefetch when prefetchCondition) {

//...

@defer (when isVisible; prefetch on immediate) {

//...

Listing 18: Prefetch conditions

Angular CLI 17 will have an automatic migration to transform the old control flow directives to the new syntax. This should simplify changing to the new syntax. But at least in Angular 17, both syntax variants can still be used.

Breaking Changes

Since Angular 17 is a major release, there’s also a few breaking changes. One breaking change concerns the Signal feature introduced in Angular 16. Signals were still in a preview mode in Angular 16, hence the short time until this change. With Signals, it’s been shown that the "signal.mutate()" function can cause inconsistencies in the application. Therefore, the option to change a Signal has been removed. Signals should no longer be changed by mutation, but should be used like an immutable data structure instead. Listing 19 shows an example of how a Signal containing an array could previously be changed and how it should be done from Angular 17 onwards.


// With Angular 16

items.mutate(itemsArray => itemsArray.push(newItem));

  

// From Angular 17:

items.update(itemsArray => [itemsArray, ...newItem]);

Listing 19: Comparing adding items to a "Signal<Array>" between Angular 16/17

Besides Signals, there are also breaking changes in the router. So far, this has the properties:

  • “canceledNavigationResolution“
  • paramsInheritanceStrategy
  • titleStrategy
  • urlUpdateStrategy
  • malformedUriErrorHandler
  • urlHandlingStrategy

With Angular 17, these properties were removed from the router’s public API. Instead of using the router, these properties should now be configured with the corresponding configuration parameters in the "provideRouter()" function or the "RouterModule.forRoot()" hook.

Angular 17 in Practice

The changes allow new types of architecture and optimizations which were previously impossible or difficult to implement. An example from practice illustrates this:

A played a leading role in developing an integrated platform to provide effective, secure, fully GDPR-compliant remote training. If you look at a live screenshot (Figure 3), you’ll see that the audio and video control is directly available in the primary view (bottom left switch). This means that the associated components and services cannot be reloaded later with lazy loading, since there’s no nested route that would allow the code to be split accordingly.

This is now possible thanks to the new option to carry out code splitting and lazy loading at the component level. This means that initial application loads are considerably faster, since audio/video functionality in particular is complex and accounts for large portions of the code.

angular17_2

Figure 3: A complex Angular real-time application

It is also advantageous to be able to do without zone.js and implement better controlled and fine-grained change detection using Signals. This leads to an overall improved performance and reduced load on the client system.

On the other hand, this requires corresponding changes that cannot be implemented by an automatic code transformation. All involved developers must learn the new concepts and master them in depth in order to effectively and meaningfully use these new possibilities.

By the way, what about the Angular module system? It still exists today, but the plan is to make modules optional in the future. This is also intended to make it easier for new developers to get started with Angular.

Angular Material

Angular Material 17 introduces both an important and significant change for developers still using old Material components, known as “legacy” since Angular 15. Angular 15 is called legacy because the Angular Material team decided to replace the previously used CSS with the official Google Material Design Components (MDC) style. Because some components needed to be significantly revised, the Angular Material team prefixed the old versions with "Legacy" and deprecated them, but they are still allowed to be used.

In Angular Material 17, all legacy components with a modern MDC counterpart will be removed. This means that applications must be migrated to the new MDC-based Material components by Angular 17.

In the JavaScript universe, there are several ways to work with dates. To be able to use these different date-time libraries in Angular Material as well, Angular Material offers a "DateAdapter". The DateAdapter is then used by the Datepicker, for example, for date management. The "DateAdapter" is an abstract class that must be implemented to use a specific date-time library. Angular Material includes the built-in "NativeDateAdapter", which simply uses the native "Date" object available in the browser for data management.

This "NativeDateAdapter" has previously received the "Platform" service via constructor injection. With Angular Material 17, this is no longer the case. In addition, the "NativeDateAdapter" no longer uses constructor injection generally, but the "inject()" function instead. This major change has an impact in particular, if you have created the NativeDateAdapter yourself using a constructor, for example in unit tests. This is no longer possible, instead the NativeDateAdapter must be created using Angular's dependency injection.

Important Changes in CSS

To integrate Angular Material styles into SCSS, the "@import" mechanism of SCSS could be previously used. However, since this mechanism has long since been deprecated on the SCSS side, the "@import" option has now also been removed on the Angular Material side. As of Angular Material 17, the new SCSS "@use" syntax must be used to import the Material styles.

If you want to use Angular Material with custom styling, you can do so using the Material theming API. Previously, Material did not check whether the custom themes were formally correct but with Angular Material 17, these are now strictly validated. For example, if the "mat.button-typography" mixin is used with a theme in which "typography: null" is set, there will now be an error during the SCSS build.

In addition to importing the global Material styles, it’s also possible to load only the styles for individual components in the build. To do this, you can use the SCSS mixin "mat.-theme", where "" corresponds to the component name, for example "card", "checkbox", "radio", or "list".

Additionally, you can also load only individual parts of the component styles, such as the button typography (via the mixin "mat.-typography"), the colors (via the mixin "mat.-color"), or the density styles (you could also say "spacing styles", the mixin is "mat.-density"). With Angular Material 17, even more styles have been added that are available in the component "-theme" mixins, but not in the sub-mixins. To use these styles, you must then use the "mat.-base" mixin. To add the basic styles for all components, there is now also the mixin "mat.all-component-bases".

Conclusion

Is Angular still known as the framework with long-term investment security? Is Angular’s popularity waning or is the framework able to conquer new target groups without alienating existing teams and developers? Or is there even a plan to consciously abandon existing teams and developers in order to modernize Angular?

Angular is being renewed so quickly that it feels almost like a new framework altogether. It will be interesting to see how long the old concepts will continue to be supported. After all, an ng-upgrade transformation of the source code is not enough;users also need to learn new concepts and replace old ones, which can temporarily reduce productivity. To what extent do third-party libraries support old and new Angular concepts? This is an important question, as the Angular team's communication with existing users could be improved. Additionally, users are concerned about Google's history of discontinuing products, which raises doubts about Angular's long-term viability.

The new Angular features are well-designed and enable new architectures and even more high-performance front-end applications. They also significantly improve the developer experience, especially by providing options for expressing if-elseif-else statements.

The rapid development pace of recent Angular versions could be challenging especially for enterprise customers and large projects with long-term maintenance requirements. Existing users may find it difficult to keep up, which could lead to frustration and concerns about investments made, and could ultimately limit Angular's adoption and traction with new users.

The Angular team could certainly work on improving its relationship with users by communicating more sensitively and carefully considering the risks and benefits of new features. Java is a good example of this: it consistently innovates, but with enough lead time and maximum compatibility even with very old codebases. Which is why Java is the preferred language in the enterprise environment.

The Angular Ivy migration and Java Loom project would be a good comparison, massive benefits were created through high-quality engineering, without requiring developers to make massive efforts themselves. It’s clear that this can’t always be achieved, but it’s perhaps worth keeping this in mind as a goal.

For Angular beginners, it's best to orient yourself with the latest concepts and avoid outdated literature, blog posts, and training courses.

Karsten Sitterberg
Karsten Sitterberg

Karsten Sitterberg holds a master's degree in Physics and is an Oracle-certified Java developer who specialises in modern Web APIs and frameworks such as Angular, Vue, and React. In his workshops, articles, and presentations, he regularly reports on new trends and interesting developments in the world of frontend tech. He is the co-founder of the meetup series "Frontend Freunde," the Java User Group Münster, and the Münster Cloud Meetup.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK