Quantcast
Channel: Articles by thoughtram

Easy Dialogs with Angular Material

$
0
0

Angular comes with a dedicated UI library that implements Google’s Material Design with Angular components - Angular Material. The idea is to have a consistent design language across all apps that not only looks and feels good, but also comes with a great user experience and built-in accessibility. This turns out to be very powerful as we as consumers of the platform get things like custom theming and high-quality components for free.

One component that’s often needed in different applications but not trivial to implement, is a dialog or modal. Modals need to magically create an overlay and somehow position themselves correctly so that they are always exactly in the middle of the screen. In addition to that, we often want to react to when a user interacts with a modal. Whether they are pressing a button that will close the modal, or simply hitting the escape key to dismiss it right away.

In this article we’ll take a look at how to leverage Angular Material’s built-in MatDialog to create easy to maintain dialogs and modals in our apps!

Motivation

The best way to learn a new skill is when we have a concrete use case for the thing we want to learn. For the sake of this article, we’ll use a scenario that has actually seen the light of a real world production app.

You may or may not have heard that we’re working on MachineLabs, a platform to run Machine Learning experiments in the browser. One essential UI component there is the file tree of the in-browser editor. In fact, we touched on that component in our article on custom theming. Users can create files and folders, edit them and delete them. Every time a user adds or edits a file or folder, a dialog pops open where users have a chance to decide on a name for the thing they edit or create.

This is a great scenario to learn about Angular Material’s dialog as we not only get to learn how to create and close them, but also how to use the same dialog for different actions, feeding it with different data depending on a certain context.

Let’s get right to it!

Creating dialogs with MatDialog

Angular Material comes with a very easy to use API to create dialogs using the MatDialog service. In order to get hold of it we first need to import the MatDialogModule into our application’s NgModule like this:

import{NgModule}from'@angular/core';import{MatDialogModule}from'@angular/material';@NgModule({...imports:[...MatDialogModule]})exportclassAppModule{}

Remember that as of Angular Material 2.0.0-beta.10MaterialModule is deprecated, which is why we’re importing MatDialogModule here right away. However, best practice is rather to create your own MaterialModule that imports and exports the needed material modules. We keep it simple here for the sake of this article.

Now that we’ve imported MatDialogModule we can start using its services and directives. Let’s take a look at where and when we want to create a dialog. As mentioned, the file tree allows users to add or edit files and folders. Too keep things simple, let’s start of with opening a dialog when the “add file” button is clicked.

Here’s what the template of the file tree could look like:

<ul><li*ngFor="file of files">
    {{file.name}}<li></ul><button(click)="openAddFileDialog()">Add file</button>

The corresponding component looks something like this (again, simplified):

@Component(...)exportclassFileTreeComponent{openAddFileDialog(){}}

Great. Now all we have to do is to use Angular Material’s MatDialog service to create a dialog. To do that we need to inject an instance of that service into our component and tell it what component type to use to create such a dialog using its open() method. Let’s say we create a component FileNameDialogComponent which takes care of showing an input control so users can enter a name of a new file.

import{MatDialog,MatDialogRef}from'@angular/material';import{FileNameDialogComponent}from'../file-name-dialog';@Component(...)exportclassFileTreeComponent{fileNameDialogRef:MatDialogRef<FileNameDialogComponent>;constructor(privatedialog:MatDialog){}openAddFileDialog(){this.fileNameDialogRef=this.dialog.open(FileNameDialogComponent);}}

Another thing we need to do is to add FileNameDialogComponent to our application module’s entryComponents, since it’s dynamically created at runtime.

@NgModule({...declarations:[...FileNameDialogComponent],imports:[...MatDialogModule],entryComponents:[FileNameDialogComponent]})exportclassAppModule{}

There are a couple of things to note here. We’re injecting MatDialog into our component and call its open() method with the FileNameDialogComponent type when a user wants to add a file. MatDialog#open() returns a MatDialogRef which is, as the name states, a reference to the now created dialog. Yeap, the dialog has already been created with just this little amount of code.

The dialog reference is important because it lets us react to when something with our dialog happens. Also, it turns out that we have access to the same reference inside the dialog itself, enabling us to control the dialog from there. We’ll see in a second when this is useful.

Let’s reward ourselves first and take a look at what we’ve already created:

Configuring dialogs

Every dialog created using MatDialog already comes with a decent default behaviour and default configuration. However, we can still tune things to our needs. This includes things like the width or height of the dialog. Or whether the dialog should have a backdrop or not. Dialog configuration can be easily passed as a second argument to MatDialog#open() as an object like this:

@Component(...)exportclassFileTreeComponent{...openAddFileDialog(){this.fileNameDialogRef=this.dialog.open(FileNameDialogComponent,{hasBackdrop:false});}}

For a full list of configuration options checkout the dedicated API documentation.

Adding Material look & feel

Let’s take care of giving our dialog a more Material look & feel. This is obviously not a requirement as we’re free to style our dialogs the way we want, but for the sake of this article we stick with what Angular Material has to offer right out of the box.

To make our dialogs look more like they come straight our of Google’s offices, Angular Material comes with a couple of directives that we can use, which take care of adding a Material Design look & feel to our component. Those directives are:

  • [mat-dialog-title] - Renders a nice looking dialog title in Material Design
  • mat-dialog-content - Takes care of rendering a consistent dialog content area for things like texts, forms etc.
  • mat-dialog-actions - Good for action elements like buttons to confirm or close a dialog

Alright, that’s use those in our FileNameDialogComponent:

@Component({template:`<h1mat-dialog-title>Addfile</h1><mat-dialog-content>Contentgoeshere</mat-dialog-content><mat-dialog-actions><buttonmat-button>Add</button><buttonmat-button>Cancel</button></mat-dialog-actions>`})exportclassFileNameDialogComponent{}

Okay, this already looks much better. Next we take care of accessing data returned by a dialog.

Returning data from dialogs

Now that our dialog looks good as well, we need to find a way to let the user enter a file name and once confirmed, taking that name and create a new file object in our application. As mentioned earlier, each MatDialogRef gives us APIs to react to events emitted by a dialog. When a dialog gets closed, either by hitting the escape key or by closing it using APIs, the afterClosed() Observable emits. From within the dialog, we can control if and what gets emitted by the dialog when it’s closed, giving us all the tools we need complete implementing this feature.

Let’s first take care of emitting the file name entered by the user after closing the dialog. To do that we create a small form within our FileNameDialogComponent which will close the dialog once it’s submitted.

import{Component,OnInit}from'@angular/core';import{MatDialogRef}from'@angular/material';import{FormGroup,FormBuilder}from'@angular/forms';@Component({templateUrl:'./file-name-dialog.component'})exportclassFileNameDialogComponent{form:FormGroup;constructor(privateformBuilder:FormBuilder,privatedialogRef:MatDialogRef<FileNameDialogComponent>){}ngOnInit(){this.form=this.formBuilder.group({filename:''})}submit(form){this.dialogRef.close(`${form.value.filename}`);}}

Notice how we inject MatDialogRef<FileNameDialogComponent>. Yes, this is exactly the same reference we have access to from within our FileTreeComponent. MatDialogRef has a method close() which will essentially close the dialog. Any data that is passed to that method call will be emitted in its afterClosed() stream. Since the template got a little bigger now, we’ve extracted it into its own template file.

Here’s what it looks like:

<form[formGroup]="form"(ngSubmit)="submit(form)"><h1mat-dialog-title>Addfile</h1><mat-dialog-content><mat-form-field><inputmatInputformControlName="filename"placeholder="Enter filename"></mat-form-field></mat-dialog-content><mat-dialog-actions><buttonmat-buttontype="submit">Add</button><buttonmat-buttontype="button"mat-dialog-close>Cancel</button></mat-dialog-actions></form>

One thing to point out here is that we use the mat-dialog-close directive, which is kind of the equivalent to (click)="dialogRef.close()", just that we don’t have to type it out every time. If all these forms APIs are new to you, we recommend checkout out our articles on Template-driven Forms and Reactive Forms in Angular.

Great! Now that our dialog emits the entered file name, we can access it from within FileTreeComponent and create a new file object. In order to do that, we subscribe to fileNameDialogRef.afterClosed(). We also need to make sure that we only perform our file object creation when the emittion has an actual value and isn’t an empty string. This can be done easily by using Reactive Extensions and its filter operator (obviously we should add some validation for that but let’s not get distracted too much here).

...import{filter}from'rxjs/operators';@Component(...)exportclassFileTreeComponent{...openAddFileDialog(){this.fileNameDialogRef=this.dialog.open(FileNameDialogComponent,{hasBackdrop:false});this.fileNameDialogRef.afterClosed().pipe(filter(name=>name)).subscribe(name=>this.files.push({name,content:''}));}}

That’s it! We can now add new files to our file tree via our brand new dialog. As mentioned earlier, we would also obviously take care of some validation, such as not allowing the user to submit the form when no file name has been entered. Another thing we might want to ensure is that no duplicated files can be created. However, this is out of the scope of this article.

Here’s our app in action:

Sharing data with dialogs

There’s one more thing we need to implement to make our dialog also work for scenarios where users want to edit an existing file name - sharing data between dialogs. When users edit a file name, we most likely want to simply reuse the same dialog we’ve just created because it’s essentially exactly the same form with the same rules and same behaviour, just that it should be pre-filled with the name of the file to edit. In other words, we need to find a way to pass data to the dialog that’s going to be opened.

Luckily, this is quite easy because Angular Material got us covered! It turns out that we can pass any data we want to a dialog using its configuration when the dialog is created. All we have to do is to attach the data we need inside the dialog to the configuration’s data property.

Since we want to use the same dialog for both actions, let’s also rename openAddFileDialog() to openFileDialog() and give it an optional file parameter. Here’s what that would look like:

<ul><li*ngFor="file of files">
    {{file.name}}<button(click)="openFileDialog(file)">Edit file</button><li></ul><button(click)="openFileDialog()">Add file</button>

Now, we also need to check inside our component whether a file has been passed to that method or not, and pass it on to the dialog like this:

@Component(...)exportclassFileTreeComponent{...openFileDialog(file?){this.fileNameDialogRef=this.dialog.open(FileNameDialogComponent,{hasBackdrop:false,data:{filename:file?file.name:''}});...}}

All we need to do now is taking this data in our dialog and pre-fill our the form control accordingly. We can inject any data that is passed like that using the MAT_DIALOG_DATA injection token.

import{Component,OnInit,Inject}from'@angular/core';import{MatDialogRef,MAT_DIALOG_DATA}from'@angular/material';@Component({templateUrl:'./file-name-dialog.component'})exportclassFileNameDialogComponentimplementsOnInit{form:FormGroup;constructor(privateformBuilder:FormBuilder,privatedialogRef:MatDialogRef<FileNameDialogComponent>,@Inject(MAT_DIALOG_DATA)privatedata){}ngOnInit(){this.form=this.formBuilder.group({filename:this.data?this.data.name:''})}}

Last but not least we need to ensure that when the dialog is closed, we update the file that has been edited instead of adding a new one. We keep it simple and just look for the index of the file that’s being edited and replace it with the updated one.

...@Component(...)exportclassFileTreeComponent{...openAddFileDialog(file?){...this.fileNameDialogRef.afterClosed().pipe(filter(name=>name)).subscribe(name=>{if(file){constindex=this.files.findIndex(f=>f.name==file.name);if(index!==-1){this.files[index]={name,content:file.content}}}else{this.files.push({name,content:''});}});}}

Again, this is a trivial file tree implementation. In a real-world app we probably want to take care of having nested directories as well, which changes the level of complexity dramatically here. However, since this article is really all about how easy it is to create dialogs using Angular Material, we stick with what we have.

Where to go from here

This was it! Even though something like creating dialogs is usually rather tricky, with a UI library like Angular Material, this task really becomes a breeze. In fact, over at MachineLabs we have created several dialogs in different places of the application because it’s such an easy thing to do with given tools at hand.

Where do we go from here? Using the built-in dialog APIs, we get pretty far and only more sophisticated scenarios require a bit more brain work. For example, one thing we also did at MachineLabs was creating our own custom overlay so we could create Google Drive-like file preview.

In our next article we’ll explore how to create custom overlays and overlay services using the Angular Component Development Kit!


Custom Overlays with Angular's CDK

$
0
0

You have probably heared of Angular Material haven’t you? If you haven’t, it’s a library that provides you with high-quality Material Design components for Angular. Material Design itself is a visual design language that aims for consistency of user experience across all platforms and device sizes. That’s cool but what if your company has its own opinions about styles and the overall look and feel of the UI? How do we get the best of Angular Material without adopting the Material Design visual language?

Tada 🎉! That’s where Angular Material’s Component Dev Kit (CDK for short) comes into play. The CDK provides us with tools to build awesome and high-quality Angular components without adopting the Material Design visual language. Its goal is to make our life as developers easier and extract common behaviors and patterns shared between multiple Angular Material components. For instance, the datepicker, snackbar, or tooltip have something in common; they need to dynamically open up some floating panel on the screen. But that’s just the tip of the ice berg. There are many different packages for all sorts of things such as a11y that helps us improve the accessibility of our UI components. There’s even a layout package with utilities to build responsive UIs that react to screen-size changes. For a more complete list, please check out the official documentation.

Over at MachineLabs, we thought it would be useful to provide a way to preview generated output files (mostly images), so users don’t have to download it every single time just to take a quick look. So we sat down to build a Google Drive like overlay with the CDK. This post is meant to share our knowledge with the community and to make you comfortable using the CDK for your own purposes.

In this post, we’ll use the CDK to build a Google Drive-like custom overlay that looks and feels much like the one built for MachineLabs. Here’s how it looks like:

overlay preview

The building blocks

Let’s start simple and work our way up to the final, fully-fledged solution which will have a very similar API as the MatDialog service provided by Angular Material. It’s not important to know exactly how the MatDialog works but it’s definitely helpful. If this is new to you, we recommend to check out our post on Easy Dialogs with Angular Material.

Our solution will be a little less flexible but specifically made for showing a file preview inspired by Google Drive. That said, we’d like to have a nice toolbar at the top and the image being rendered in the middle of the screen.

In general, the MatDialog is great for showing content in a dialog box but as soon as we want a little bit of a custom look and feel, something that does not look like a white box with content inside, we would need to roll our own overlay. Luckily, we can use the overlay package from the CDK that has most of the core logic for opening floating panels already baked in. More on that in just a second.

Here are the core building blocks of our application:

application architecture

As we can see, we have two components, one service and a class that represents a remote control to an opened overlay. The AppComponent is the root (or entry point) of our application. This component contains a toolbar and the list of files that we can preview. In addition, it has access to a FilePreviewOverlayService which provides us with the core logic for opening an overlay. At the same time it’s an abstraction for some “heavy” lifting that should be implemented in a resuable manner. Don’t be scared, it’s not going to be super heavy and we’ll break it down into comprehensible chunks. Last but not least, there’s a FilePreviewOverlayRef which, as mentioned, is a handle used to control (e.g. close) a particular overlay.

For the overlay we choose to render a component, so we can attach some logic and also add animations to our overlay to engage our users and make them happy. We call this component FilePreviewOverlayComponent.

That’s about it. Now that we have the basic structure in place, we’re ready to look at some code.

Note that this post is the first part out of two in which we lay the foundation for our custom overlay. We’ll build on top of this in the next part and add keyboard support, image preloading and animations.

Setup

Before we can start implementing the custom overlay we need to install the CDK. Simply run npm install @angular/cdk and we’re all set!

Our first overlay

From the MatDialog we know that when we open an overlay we must specify a component type that is then created dynamically at runtime. This means it is not created by using the component tags inside an HTML template. Also, we know that whenever a component is created at runtime, we must add it to our application module’s entryComponents.

Let’s do that and add the FilePreviewOverlayComponent to the array of entryComponents. In addition, we need to add the OverlayModule to the imports list of the root AppModule:

import{OverlayModule}from'@angular/cdk/overlay';...@NgModule({imports:[...],declarations:[...,FilePreviewOverlayComponent],bootstrap:[AppComponent],providers:[...],entryComponents:[// Needs to be added here because otherwise we can't// dynamically render this component at runtimeFilePreviewOverlayComponent]})exportclassAppModule{}

From there, creating an overlay is easy. First, we inject the Overlay service. This service has a create() function that we need to call in order to create a PortalHost for our FilePreviewOverlayComponent. Finally we need to create a ComponentPortal from this component and attach it to the PortalHost. Wait, what? Let’s give it a moment and look at some code before taking it apart:

@Injectable()exportclassFilePreviewOverlayService{// Inject overlay serviceconstructor(privateoverlay:Overlay){}open(){// Returns an OverlayRef (which is a PortalHost)constoverlayRef=overlay.create();// Create ComponentPortal that can be attached to a PortalHostconstfilePreviewPortal=newComponentPortal(FilePreviewOverlayComponent);// Attach ComponentPortal to PortalHostoverlayRef.attach(filePreviewPortal);}}

The first step is to create a PortalHost. We do that by calling create() on the Overlay service. This will return an OverlayRef instance which is basically a remote control for the overlay. One unique attribute of this OverlayRef is that it’s a PortalHost, and once created, we can attach or detach Portals. We can think of a PortalHost as a placeholder for a component or template. So in our scenario, we are creating a ComponentPortal that takes a component type as its fist argument. In order to actually display this component we need to attach the portal to the host.

Ok, but where does the overlay get rendered?

Good question. There’s an OverlayContainer service which creates a container div under the hood that gets appended to the body of the HTML Document. There are a few more wrapper elements created but our component eventually ends up in a div with a class of cdk-overlay-pane. Here’s what the DOM structure looks like:

<divclass="cdk-overlay-container"><divid="cdk-overlay-0"class="cdk-overlay-pane"dir="ltr"><!-- Component goes here --></div></div>

Done. That’s all we need to create our very first custom overlay using the CDK. Let’s try it out and see what we got so far:

Our service only exposes one public method open() that will take care of creating a custom overlay. For now, the service is quite simple but it gets more complicated as we implement a more sophisticated and complete (functional-wise) overlay. Therefore it’s a good idea to extract the common logic into a service to stay DRY. Imagine we would have the same logic defined in each component we want to show an overlay. No good, right?

Now that we have layed the foundation for our custom overlay, let’s take it one step further and improve on what we have so far. Let’s add a backdrop and specify a scroll and position strategy. Don’t worry if it’s unclear what scroll and position strategy is all about. We’ll cover that in a second.

Configuring the overlay

When creating an overlay, we can pass an optional configuration object to create() to set the desired options, e.g. whether it has backdrop, the position or scroll strategy, width, height and many more. Here’s an example:

// Example configurationoverlay.create({width:'400px',height:'600px'});

First of all, we allow the consumer of our API to override certain options. Therefore, we update the signature for open() to also take a configuration object. In addition, we define an interface that describes the shape of the configuration from a consumer perspective:

// Each property can be overridden by the consumerinterfaceFilePreviewDialogConfig{panelClass?:string;hasBackdrop?:boolean;backdropClass?:string;}@Injectable()exportclassFilePreviewOverlayService{open(config:FilePreviewDialogConfig={}){...}}

Next, we define some initial values for the config, so that, by default, every overlay has a backdrop alongside a backdropClass and panelClass:

constDEFAULT_CONFIG:FilePreviewDialogConfig={hasBackdrop:true,backdropClass:'dark-backdrop',panelClass:'tm-file-preview-dialog-panel'}@Injectable()exportclassFilePreviewOverlayService{...}

With that in place, we can define a new method getOverlayConfig() which takes care of creating a new OverlayConfig for the custom overlay. Remember, it’s better to break down the logic into smaller parts instead of implementing everything in one giant function. This ensures better maintainability but also readability of our code.

@Injectable()exportclassFilePreviewOverlayService{...privategetOverlayConfig(config:FilePreviewDialogConfig):OverlayConfig{constpositionStrategy=this.overlay.position().global().centerHorizontally().centerVertically();constoverlayConfig=newOverlayConfig({hasBackdrop:config.hasBackdrop,backdropClass:config.backdropClass,panelClass:config.panelClass,scrollStrategy:this.overlay.scrollStrategies.block(),positionStrategy});returnoverlayConfig;}}

Our method is quite simple. It takes a FilePreviewDialogConfig and creates a new OverlayConfig with the values from the given configuration. However, there are two important things to mention. One is the scrollStrategy and the other one is the positionStrategy.

Scroll strategy

The scroll strategy is a way of defining how our overlay should behave if the user scrolls while the overlay is open. There are several strategies available as part of the CDK, such as

  • NoopScrollStrategy: does nothing
  • CloseScrollStrategy: automatically closes the overlay when scrolling
  • BlockScrollStrategy: blocks page scrolling
  • RepositionScrollStrategy: will reposition the overlay element on scroll

For our file preview overlay, we are going to use the BlockScrollStrategy because we don’t want the user to be scrolling in the background while the overlay is open.

The scrollStrategy takes a function that returns a scroll strategy. All strategies are provided by the Overlay service and can be accessed via the scrollStrategies property:

constoverlayConfig=newOverlayConfig({...// Other strategies are .noop(), .reposition(), or .close()scrollStrategy:this.overlay.scrollStrategies.block()});

If we don’t specify a strategy explicitly, all overlays will use the NoopScrollStrategy.

Position strategy

The position strategy allows us to configure how our overlay is positioned on the screen. There are two position strategies available as part of the CDK:

  • GlobalPositionStrategy: used for overlays that need to be positioned unrelated to other elements on the screen. This strategy is mostly used for modals or root-level notifications.
  • ConnectedPositionStrategy: used for overlays that are positioned relative to other elements. This is commonly used for menus or tooltips.

We’ll be using the GlobalPositionStrategy for our overlay because it’s supposed to be positioned globally on screen, unrelated to other elements.

Similar to the scrollStrategy we can access all position strategies through the Overlay service like so:

constpositionStrategy=this.overlay.position().global().centerHorizontally().centerVertically();constoverlayConfig=newOverlayConfig({...positionStrategy});

With the configuration in place, we go ahead and define another method createOverlay() that hides the complexity of creating an overlay with a given configuration:

@Injectable()exportclassFilePreviewOverlayService{...privatecreateOverlay(config:FilePreviewDialogConfig){// Returns an OverlayConfigconstoverlayConfig=this.getOverlayConfig(config);// Returns an OverlayRefreturnthis.overlay.create(overlayConfig);}}

We now refactor our open() method to generate a default config and utilize createOverlay():

exportclassFilePreviewOverlayService{...open(config:FilePreviewDialogConfig={}){// Override default configurationconstdialogConfig={...DEFAULT_CONFIG,...config};constoverlayRef=this.createOverlay(dialogConfig);...}}

Here’s what it looks like in action:

Our overlay looks much more like an overlay as we have imagined it in the beginning. The good thing is that most of the heavy lifting is taken care of by the CDK, such as dynamically creating a component, block page scrolling, or positioning.

So far, so good, but we are still missing some very fundamental functionality. We can open an overlay but what about closing it? This it not yet possible, so let’s go ahead and add this feature.

Closing overlays with a remote control

Just like we use remote controls to snap between television channels, we want a remote control to close our overlays. It will provide an API for modifying, closing, and listening to events on the overlay instance. Especially if we want to be able to close the dialog from within the overlay component, and optionally return a value to the consumer.

Our remote control will be a simple class that exposes only one public method - close(). For now we keep simple and extend it as we introduce more features. Here’s what it looks like:

import{OverlayRef}from'@angular/cdk/overlay';exportclassFilePreviewOverlayRef{constructor(privateoverlayRef:OverlayRef){}close():void{this.overlayRef.dispose();}}

When implementing the remote control, the only thing we have to make sure is that we need access to the OverlayRef. It’s a reference to the overlay (portal host) that allows us to detach the portal. Note that, there’s no @Injectable decorator attached to the class which means that we can’t leverage the DI system for this service. This, however, is no big deal because we will manually create an instance for every overlay and therefore we don’t need to register a provider either. Theoretically, we could open multiple overlays stacked on top of each other where each overlay has its own remote control. The DI system creates singletons by default. That’s not what we want in this case.

What’s left to do is to update our open() method to create a remote control and return it to the consumer of our API:

@Injectable()exportclassFilePreviewOverlayService{...open(config:FilePreviewDialogConfig={}){...constoverlayRef=this.createOverlay(dialogConfig);// Instantiate remote controlconstdialogRef=newFilePreviewOverlayRef(overlayRef);...// Return remote controlreturndialogRef;}

Notice how we pass in the overlayRef when creating a new FilePreviewOverlayRef? That’s how we get a hold of the PortalHost inside the remote. Instead of implementing a class that represents a reference to the open overlay, we could have returned the OverlayRef directly. However, it’s not a good idea to expose lower-level APIs because users could mess with the overlay and detach the backdrop for instance. Also, we need a little bit more logic later on when we introduce animations. A remote control is a good way of limiting the access to the underlying APIs and expose only those that we want to be publicly available.

From a consumer perspective we now get a handle to the overlay that allows us to programatically close it at some point. Let’s go ahead and update AppComponent accordingly:

@Component({...})exportclassAppComponent{...showPreview(){// Returns a handle to the open overlayletdialogRef:FilePreviewOverlayRef=this.previewDialog.open();// Close overlay after 2 secondssetTimeout(()=>{dialogRef.close();},2000);}}

Here’s our code in action. Remember, once we open an overlay it will automatically close after 2 seconds:

Awesome! We are making serious progress and it’s not far until we reach the top of the mountain.

Improving ergonomics

In the previous sections we have mainly improved the overlay under the hood and layed a foundation for upcoming features. In this section we want to focus on improving the overlay’s ergonomics. This means that we want to be able to close the dialog when we click on the backdrop.

Turns out that the backdrop logic is extremely easy with the CDK. All we have to do is to subscribe to a stream that emits a value when the backdrop was clicked:

@Injectable()exportclassFilePreviewOverlayService{open(config:FilePreviewDialogConfig={}){...// Subscribe to a stream that emits when the backdrop was clickedoverlayRef.backdropClick().subscribe(_=>dialogRef.close());returndialogRef;}}

That’s it! Imagine how much work this would be without the CDK.

From here we could take it one step further and also close the overlay when a user naviagtes back in the browser history. For our application, however, this doesn’t make much sense because we are not using the router and there’s only one page that we render out to the screen. But feel free to give it a shot! Hint: use the Location service and subscribe to the browser’s popState events.

Sharing data with the overlay component

The goal of this post was to implement a generic file preview dialog rather than a static one. At the moment the overlay is quite static and there’s no way we can share data with the overlay component. Sharing data means we want to be able to provide an image that will be available within the component. After all it’s supposed to be a file preview. Therefore, we need to think about how we can share data with the component that is dynamically created.

Luckily, Angular has a hierarchical dependency injection system (DI for short) that we can leverage for our purpose. For more information on Angular’s DI system, check out this post.

In a nutshell, the DI system is flexible enough that we can reconfigure the injectors at any level of the component tree. That said, there is no such thing as the injector. An application may have multiple injectors and each component instance has its own injector. You hear the bells ring? Right, we can create our own custom injector and provide it with a list of custom injection tokens. It sounds more complicated than it actually is.

Turns out, the CDK already has a class PortalInjector that that we can use to provide custom injection tokens to components inside a portal. This is exactly what we need. Let’s break ground and implement a function createInjector() that creates a new PortalInjector and defines a list of custom injection tokens.

@Injectable()exportclassFilePreviewOverlayService{...privatecreateInjector(config:FilePreviewDialogConfig,dialogRef:FilePreviewOverlayRef):PortalInjector{// Instantiate new WeakMap for our custom injection tokensconstinjectionTokens=newWeakMap();// Set custom injection tokensinjectionTokens.set(FilePreviewOverlayRef,dialogRef);injectionTokens.set(FILE_PREVIEW_DIALOG_DATA,config.data);// Instantiate new PortalInjectorreturnnewPortalInjector(this.injector,injectionTokens);}

In the code above we create a new WeakMap, set our custom injection tokens that we want to be available (injectable) in the overlay component, and finally instantiate a new PortalInjector. The important part though is that we also specify a parent injector (first argument) which is mandatory. Also notice the second argument where we pass in our injection tokens.

There are two things that we are providing. The first token is the FilePreviewDialogRef. Having the remote control at hand, allows the overlay component to close itself. This is very useful because there will definitely be a close button somewhere. The second token is a custom InjectionToken that stores the data that we want to share with the component.

For the InjectionToken we create new file file-preview-overlay.tokens and instantiate a new InjectionToken:

import{InjectionToken}from'@angular/core';import{Image}from'./file-preview-overlay.service';exportconstFILE_PREVIEW_DIALOG_DATA=newInjectionToken<Image>('FILE_PREVIEW_DIALOG_DATA');

Next, let’s update our FilePreviewDialogConfig so that the user can specify an image that will be used by the overlay component:

interfaceImage{name:string;url:string;}interfaceFilePreviewDialogConfig{panelClass?:string;hasBackdrop?:boolean;backdropClass?:string;data?:Image;}@Injectable()exportclassFilePreviewOverlayService{...}

For better readability we’ll also refactor our open() method and create a new attachDialogContainer() function that now takes care of creating the injector and component portal, as well as attaching the portal to the host.

@Injectable()exportclassFilePreviewOverlayService{...privateattachDialogContainer(overlayRef:OverlayRef,config:FilePreviewDialogConfig,dialogRef:FilePreviewOverlayRef){constinjector=this.createInjector(config,dialogRef);constcontainerPortal=newComponentPortal(FilePreviewOverlayComponent,null,injector);constcontainerRef:ComponentRef<FilePreviewOverlayComponent>=overlayRef.attach(containerPortal);returncontainerRef.instance;}}

With that in place, we can now update our FilePreviewOverlayComponent and inject the tokens that we have defined on a component level with the help of a custom injector.

exportclassFilePreviewOverlayComponent{constructor(publicdialogRef:FilePreviewOverlayRef,@Inject(FILE_PREVIEW_DIALOG_DATA)publicimage:any){}}

We can now define data that will be passed to the overlay component and render an image onto the screen. Here’s an example of how we can pass in data:

@Component({...})exportclassAppComponent{...showPreview(file){letdialogRef:FilePreviewOverlayRef=this.previewDialog.open({image:file});}}

Finally with a little bit of styling we come much closer to what we’re trying to achieve.

Where to go from here

This is it. Although creating custom overlays is something that’s more or less tricky to do, this task becomes rather easy with UI libraries like Angular Material that provide us with a common set of tools to build awesome and high-quality Angular components. More specifically, by extracting common behaviors and patterns into a so called Component Dev Kit, it becomes extremely easy to build a custom overlay.

Where to go from here? As mentioned in the beginning, this was only part one and we haven’t fully re-built the Google Drive-like file preview yet. In the next post we will build on top of this and implement keyboard support, image preloading and add animations in order to make our overlay more engaging.

Custom Overlays with Angular's CDK - Part 2

$
0
0

In a previous post we have layed the foundation for our custom overlay. To recap, we wanted to build a Google Drive-like custom overlay that looks and feels much like the one built for MachineLabs. Let’s have a look at a preview:

overlay preview

In the first part of this series we learned how to use Angular’s CDK to create our very first custom overlay. On top of that we made it configurable, implemented a “handle” to control (e.g. close) an opened overlay and made it possible to share data with the overlay component.

In this post, we’ll pick up from where we left off and implement a few additional features that will take our overlay to the next level. More specifically, we’ll implement keyboard support, image preloading and add animations in order to make our overlay more engaging and provide better feedback. In the end, we’ll complete this post by adding a toolbar component to fully match Google Drive’s look and feel.

Let’s dive right into it!

Adding keyboard support

Adding keyboard support is easy. All we need to do is to use the @HostListener() decorator. This decorator is a function decorator that accepts an event name as an argument. Let’s use it inside our FilePreviewOverlayComponent to listen to keydown events on the HTML Document, so that we can close the overlay whenever the escape button was pressed.

Closing the dialog from within the overlay component is only possible because the FilePreviewOverlayRef is now available via the DI system. Remember that we created our own custom injector and defined custom injection tokens for the remote control and the data that we want to share with the component.

Let’s have a look at the code:

import{...,HostListener}from'@angular/core';// Keycode for ESCAPEconstESCAPE=27;@Component({...})exportclassFilePreviewOverlayComponent{...// Listen on keydown events on a document level@HostListener('document:keydown',['$event'])privatehandleKeydown(event:KeyboardEvent){if(event.keyCode===ESCAPE){this.dialogRef.close();}}constructor(publicdialogRef:FilePreviewOverlayRef,...){}

Using the host listener we decorate a class method that is called on every keydown event. The function itself gets the KeyboardEvent that we can use to check whether it’s the escape key and only then call close() on the dialogRef.

That’s it already for adding keyboard support. Go ahead and try it out. Open a file preview and then press the escape button.

Preloading images

Instant app response is without any doubt the best, but there are cases when our apps won’t be able to deliver the content immediately, e.g. slow internet connection or even latency issues. In those cases it’s extremely important to provide users with feedback and indicate that progress is being made. It’s crucial to let the user know what is happening in contrast to keep them guessing. One of the most common forms of such feedback is a progress indicator. It reduces the user’s uncertainty, perception of time and offers a reason to wait.

Looking at our overlay, we are facing the exact same problems. When we click to preview an image, depending on the internet connection, the image is fetched by the browser and progressively rendered onto the screen. If the connection is really bad it may take a while. Also, it doesn’t really look that nice if we display image data as it is received, resulting in a top-down filling in of the image.

To solve this, we can use a progress indicator. The good thing is we don’t need to write one from scratch because Angular Material already provides a nice set of loading indicators, one of which is the <mat-spinner>. In order to use it, we need to add the MatProgressSpinnerModule from @angular/material to the imports of our application:

import{...,MatProgressSpinnerModule}from'@angular/material';@NgModule({imports:[...,MatProgressSpinnerModule],...})exportclassAppModule{}

Note that the <mat-spinner> component is an alias for <mat-progress-spinner mode="indeterminate">. As we can see, the progress-spinner supports different modes, determinate and indeterminate.

The difference is that determinate progress indicators are used to indicate how long an operation will take, whereas indeterminate indicators request that the user needs to wait while something finishes. The latter is used when it’s not necessary to indicate how long it will take or to convey a discrete progress. This is perfect for preloading images because we have no idea how long it may take to fetch the image.

Ok, now that we have added the respective module to our imports we can go ahead and update the template of the FilePreviewOverlayComponent as well as the component class:

@Component({template:`<divclass="overlay-content"><divclass="spinner-wrapper"*ngIf="loading"><mat-spinner></mat-spinner></div><img(load)="onLoad($event)"[style.opacity]="loading ? 0 : 1"[src]="image.url"></div>`,...})exportclassFilePreviewOverlayComponent{loading=false;...onLoad(event:Event){this.loading=false;}}

First off, we introduce a new property loading and initialize it with a meaningful value, e.g. false. This will show our spinner until the image is loaded. Also note that we are using a property binding to set the opacity of the <img> element to 0 when it’s loading and 1 when it’s finished. If we didn’t do this, we’d still see the image being rendered or filled in from top to bottom. This is just a temporary solution that we will replace with a proper solution using Angular’s Animation DSL in just a moment. Last but not least, we define a success callback as a method on our class that is called when the image is loaded. The callback is hooked up in the template via an event binding. In this particular case we are listening for the load event and when fired, we call the onLoad() method.

One more thing to mention is that we needed to wrap the spinner in another element. The reason for this is that we want the spinner to be vertically and horizontally centered. To achieve this we would leverage the CSS transform property to apply a transformation to the spinner element. The problem is that the <mat-spinner> component is animated with CSS transforms meaning every transformation we set on the element is overridden. Therefore we use a container that wraps the spinner, so that we can savely apply transformation and center it on the screen.

Here’s the image preloading in action. To better demonstrate the loading, you can throttle your connection in the “Network” tab of Chrome’s DevTools.

Animating the overlay

With animations we aim to guide users between views so they feel comfortable using the site, draw focused-attention to some parts of our application, increase spacial awareness, indicate if data is being loaded, and probably the most important point - smoothly transition users between states.

The problem with our overlay is that it still pops right into our faces. The backdrop is already animated for us, but having an animation only for the backdrop is not enough. We also want to add a little bit of motion to the overlay component, so that it’s less surprising for the user.

If you are completely new to animations in Angular, please check out our post on the Foundation Concepts or have a look at our Web Animations Deep Dive with Angular.

Let’s start off by importing the BrowserAnimationsModule into our application’s NgModule like this:

import{BrowserAnimationsModule}from'@angular/platform-browser/animations';@NgModule({imports:[...,BrowserAnimationsModule],...})exportclassAppModule{}

With this in place, we can go ahead and define our animations with Angular’s Animation DSL and add it to the component via the animations metadata property in the @Component() decorator:

// Reusable animation timingsconstANIMATION_TIMINGS='400ms cubic-bezier(0.25, 0.8, 0.25, 1)';@Component({...animations:[trigger('fade',[state('fadeOut',style({opacity:0})),state('fadeIn',style({opacity:1})),transition('* => fadeIn',animate(ANIMATION_TIMINGS))]),trigger('slideContent',[state('void',style({transform:'translate3d(0, 25%, 0) scale(0.9)',opacity:0})),state('enter',style({transform:'none',opacity:1})),state('leave',style({transform:'translate3d(0, 25%, 0)',opacity:0})),transition('* => *',animate(ANIMATION_TIMINGS)),])]})exportclassFilePreviewOverlayComponent{...}

As you can see we created two animations, one for fading in the image (fade) and the other one to slide up the content (slideContent). The fade animation will mostly be visible in combination with a spinner. Remember how we used the property binding to temporarily make the image invisible while loading? With the fade animation we can now replace our temporary solution with proper one that leverages the animation DSL.

Next, we define an animationState property that represents the current animation state, e.g. void, enter or leave. By default it’s set to enter that will cause the content of the file preview to always slide up when it’s opened.

@Component({...})exportclassFilePreviewOverlayComponent{...animationState:'void'|'enter'|'leave'='enter';}

Now we can connect the pieces and set it up in the template:

@Component({template:`<divclass="overlay-content"[@slideContent]="animationState"><divclass="spinner-wrapper"*ngIf="loading"><mat-spinner></mat-spinner></div><img[@fade]="loading ? 'fadeOut' : 'fadeIn'"(load)="onLoad($event)"[src]="image.url"></div>`,...})exportclassFilePreviewOverlayComponent{...}

We can see that the entire content, including the spinner as well as the image, is wrapped in a container div. That’s because we only want the content to slide up. Later we’ll introduce a toolbar component which comes with its own set of animations. This also implies that it wouldn’t make any sense to use a @HostBinding() and apply the animation to the host element.

Done! Here’s a live demo with all the code above:

Animations guide our users and the overlay smoothly animates in. This is already much more engaging compared to what we had before but we can do even better. What about closing it? That’s right, we also want it to animate out.

At the moment we simply call this.overlayRef.dispose() which will detach the portal from the host, eventually removing it from the DOM. With this straightforward approach there’s no way we can execute animations before it is disposed.

What we can do instead is to leverage animation callbacks and introduce event streams that we can subscribe to. Angular provides animation callbacks that are fired when an animation is started and also when it is done.

Let’s start with the animation callbacks and hook them in the template of our FilePreviewOverlayComponent:

@Component({template:`<divclass="overlay-content"[@slideContent]="animationState"(@slideContent.start)="onAnimationStart($event)"(@slideContent.done)="onAnimationDone($event)">...</div>`,...})exportclassFilePreviewOverlayComponent{...}

The animation callbacks alone are not very useful, at least not for animating the overlay out. What’s missing is an event bus that we can use to broadcast animation events. That’s needed because when we close an overlay, we have to wait until the leaving animation is done before we can dispose the overlay. We’ll see in just a second how to use this but first let’s define a new EventEmitter inside the overlay component class:

import{...,EventEmitter}from'@angular/core';...@Component({...})exportclassFilePreviewOverlayComponent{...animationStateChanged=newEventEmitter<AnimationEvent>();}

The EventEmitter is an abstraction around a the Subject type from RxJS.

Cool, now let’s wire up the animation callbacks. To do this, we’ll define the two missing methods onAnimationStart() and onAnimationDone(). Every time one of the animation callbacks is fired, we broadcast the animation event using animationStateChanged. Moreover, we need a way to start the leave animation once we close an overlay. Therefore we’ll also add a method called startExitAnimation() that sets the animationState to leave. This will then trigger the corresponding animation.

@Component({...})exportclassFilePreviewOverlayComponent{...onAnimationStart(event:AnimationEvent){this.animationStateChanged.emit(event);}onAnimationDone(event:AnimationEvent){this.animationStateChanged.emit(event);}startExitAnimation(){this.animationState='leave';}}

We know that we programatically close an overlay using the remote control aka FilePreviewOverlayRef. So far we have no access to the overlay component from within the FilePreviewOverlayRef. To fix this we define a property componentInstance on the remote control.

exportclassFilePreviewOverlayRef{...componentInstance:FilePreviewOverlayComponent;...}

We simply set the componentInstance when an overlay is opened.

@Injectable()exportclassFilePreviewOverlayService{...open(config:FilePreviewDialogConfig={}){...// Instantiate remote controlconstdialogRef=newFilePreviewOverlayRef(overlayRef);constoverlayComponent=this.attachDialogContainer(overlayRef,dialogConfig,dialogRef);// Pass the instance of the overlay component to the remote controldialogRef.componentInstance=overlayComponent;...}}

Now we can go ahead and introduce two event streams on the FilePreviewOverlayRef. The first one emits values before the overlay is closed and the other one after is was closed. For the streams we’ll use the Subject type from RxJS and also expose public methods for each of the event streams.

exportclassFilePreviewOverlayRef{private_beforeClose=newSubject<void>();private_afterClosed=newSubject<void>();...afterClosed():Observable<void>{returnthis._afterClosed.asObservable();}beforeClose():Observable<void>{returnthis._beforeClose.asObservable();}}

From there we can use the component instance and subscribe to the animation events and act accordingly. This means that when an animation was started we detach the backdrop and call next() on _beforeClose. When an animation is finished we broadcast on _afterClosed. This also means we can dispose the overlay and remove it from the DOM.

import{filter,take}from'rxjs/operators';...exportclassFilePreviewOverlayRef{...close():void{// Listen for animation 'start' eventsthis.componentInstance.animationStateChanged.pipe(filter(event=>event.phaseName==='start'),take(1)).subscribe(()=>{this._beforeClose.next();this._beforeClose.complete();this.overlayRef.detachBackdrop();});// Listen for animation 'done' eventsthis.componentInstance.animationStateChanged.pipe(filter(event=>event.phaseName==='done'&&event.toState==='leave'),take(1)).subscribe(()=>{this.overlayRef.dispose();this._afterClosed.next();this._afterClosed.complete();// Make sure to also clear the reference to the// component instance to avoid memory leaksthis.componentInstance=null!;});// Start exit animationthis.componentInstance.startExitAnimation();}}

That’s it. Here’s a live demo:

Adding a toolbar component

To finish this up we will create a toolbar component, add animations and also make use of the events streams exposed by the remote control to animate the toolbar out before the overlay is closed.

@Component({selector:'tm-file-preview-overlay-toolbar',templateUrl:'./file-preview-overlay-toolbar.component.html',styleUrls:['./file-preview-overlay-toolbar.component.scss'],animations:[trigger('slideDown',[state('void',style({transform:'translateY(-100%)'})),state('enter',style({transform:'translateY(0)'})),state('leave',style({transform:'translateY(-100%)'})),transition('* => *',animate('400ms cubic-bezier(0.25, 0.8, 0.25, 1)'))])]})exportclassFilePreviewOverlayToolbarComponentimplementsOnInit{// Apply animation to the host element@HostBinding('@slideDown')slideDown='enter';// Inject remote controlconstructor(privatedialogRef:FilePreviewOverlayRef){}ngOnInit(){// Animate toolbar out before overlay is closedthis.dialogRef.beforeClose().subscribe(()=>this.slideDown='leave');}}

The template is very straightforward and leverages content projection to project content into the template of the toolbar component.

<!-- file-preview-overlay-toolbar.component.html --><divclass="toolbar-wrapper"><ng-content></ng-content></div>

Finally we have to use the toolbar inside the template of the overlay component:

@Component({template:`<tm-file-preview-overlay-toolbar><mat-icon>description</mat-icon>{{image.name}}</tm-file-preview-overlay-toolbar><divclass="overlay-content"...</div>`,...})exportclassFilePreviewOverlayComponent{...}

That’s pretty much! Let’s have a look at our final solution:

Machine Learning Jump Start - Online Course

$
0
0

About one and a half years ago we first started dipping our toes into the brave new world of Machine Learning. The concept of a machine figuring out how to solve problems on its own is fascinating and sticked with us. If you’ve been following along, you’ve probably noticed that, next to thoughtram, we started our second mission to help innovate the Machine Learning space and founded a new company MachineLabs, Inc.

Our ultimate goal is it to get more people into Machine Learning. Part of this mission is to provide the community with rich tools for the job. That’s what we’ve been tirelesslyworkingonforthepastcouple of months. Another part is to teach people how to get started with Machine Learning at all. Especially the latter makes a lot of sense as teaching is what we’re pretty good at.

Today we’re super excited to announce our cross-company collaboration, in which we’ll create the Machine Learning Jump Start Online Course!

Machine Learning Jump Start - Online Course

Machine Learning is already at the heart of many services of our daily use. Yet, the general perception of many developers seems to be that Machine Learning is just not for them. It’s for Phds only. It’s for Math cracks. Existing Machine Learning courses seem to feed this narrative as they mostly start with…yeah Math.

We aren’t here to bad-mouth this approach. We are here to embrace diversity and throw in a different attempt to teach Machine Learning. We want to create a new Machine Learning training experience with the following properties:

  • Beginner friendly
  • Driven by practical examples
  • Built on the MachineLabs platform (zero setup yet powerful!)
  • Knowledge builds up from higher to lower level (forget Math for a moment!)

To give you an idea, here’s the project’s teaser video!

What’s inside?

This course will take you from zero to hero by making you solve your first Machine Learning task in just 20 lines of pure Python code. After that the course dives into various topics such as:

  • Machine Learning Basics
  • Activation functions, Max Pooling and Dropouts
  • Common Keras APIs to solve your ML tasks
  • Convolutional Networks
  • Recurrent Networks
  • Reinforcement learning

… and much more!

Preorder now!

While the course isn’t yet 100 % finished, you can preorder it today and receive some exclusive benefits for early birds!

  • 70% discount compared to the regular price
  • exclusive access to the Early Access Programm starting in April 2018
  • 20 free monthly GPU hours for an entire year

Notice that we won’t charge you before the Early Access Preview begins!

The course is on sale now with a huge preorder discount! Head over to the course website and save 70% on your course!

Announcing Angular Master Class at Shopware

$
0
0

A few months have passed since our last Angular Master Class in Berlin, but now it’s finally time again: In June 2018 we are organizing a new Angular Master Class in Germany.

We are thrilled to have found in Shopware the ideal partner and a first-class venue for an unforgettable event. Shopware is a leading e-commere company with a passionate, skilled and fun team to work with.

The facilities of Shopware in Schöppingen are modern, spacious and fancy and will be more than ideal for our Angular Master Class.

The event will take place from 20th to 22nd June 2018 at Shopware in Schöppingen.

AMC Shopware Event LocationPicture by Rainer Lonsing

We will have three (3) days of fun and learning the ins and outs of Angular including things like

  • Components and Modules
  • Fetching data using Http
  • Advanced Dependency Injection
  • Basic Forms
  • Observables deep-dive
  • Architectural patterns
  • Component communication
  • ContentChildren and ViewChildren
  • ngrx

…and much more!

During the day we will deepen our Angular knowledge together and at the end of the day we can have a nice time playing volleyball, table soccer and enjoy some delicious food.

This will be so much fun!

AMC Shopware Volleyball court

AMC Denmark audience

Left picture by Rainer Lonsing

Who wouldn’t want to join in? So better be quick and get your ticket soon.

Tickets on sale now

The early bird round of tickets is on sale now. You can save up to 125,00 € so you’ll better be fast. Seats are very limited with only 5 early bird tickets available!

You can find more information on our official event page, or grab your public training ticket right here!

See you in Berlin!

A simple guide to Reinforcement Learning

$
0
0

This is the first post in a series of articles on Reinforcement Learning which is a subfield of Machine Learning on that we have bloggedaboutbefore.

Machine Learning is all about having a piece of software learn to solve tasks that it was not explicitly programmed for. Instead, it learns by observing data with the goal to find patterns that it can generalize into certain rules. In other words, instead of having to explicitly define rules using traditional programming constructs such as conditions, loops or function calls, we’ll let a machine figure out those rules in an automatic iterative process.

Most forms of Machine learning still relies on human input though. In particular, humans are needed to select and provide lots of data for the machine to learn from. In contrast, Reinforcement Learning takes the idea another step forward. Instead of providing datasets, we let the machine take actions in the environment that we want it to master and give it feedback on its actions in the form of rewards or punishment. In other words, the machine has no clue about the task it is trying to solve. It basically starts acting randomly at first, but develops a policy to solve the task over time. Doesn’t that sound exciting?

A word of warning

This post aims to provide an easy entry for beginners who are new to Reinforcement Learning or even Machine Learning in general. We may oversimplify or sacrifice performance in certain areas to achieve this goal. We'll also choose a simpler vocabulary over a more scientific one whenever possible.

In this article we want to learn the basics of Reinforcement Learning using a simple Q-Table approach. Don’t worry if that doesn’t ring a bell yet. We’ll learn what all of this means as we go.

We’ll be taking things further in follow-up articles in which we’ll do Deep Reinforcement Learning using a neural net instead of the Q-Table. Step by step we’ll learn about the explore-exploit dilemma, replay memory and many other exciting things. But let’s start simple!

Defining a simple game

As our main goal is to learn Reinforcement Learning, let’s keep the actual task that we want our machine to learn as simple as possible. In fact let’s just make up a simple math game ourselves! We’ll call it “Catch 5” and these are the rules:

  1. Every game starts by revealing a random number between 1 and 12 with the exception of the number 5 that will never be the starting point.

  2. From that given starting point, it is our goal to get to number 5 by simple addition/subtraction. However, we can only choose from the following six actions: Add 3, Add 2, Add 1, Subtract 1, Subtract 2 and Subtract 3

  3. We can only play a maximum of three turns. If we don’t get to 5 within three turns, we have lost the game. Since the starting number is between 1 and 12, it is always possible to win the game if we don’t screw up the basic math :)

Before we move on, let’s get familiar with the game itself. We’ve created a browser based version that you can play with right here:

Our game may turn out like this and we win in two turns.

Game: Starting number is 9. Catch 5!

Player: playing -3

Game: You played -3, now at 6. Catch 5!

Player: playing -1

Game: Caught 5! You won in 2 turns.

However, keep in mind that our machine has no idea about the rules of the game at first and basically just “sees” six buttons without any description on how to play. It doesn’t even know it’s playing a game, so it may act like the following and lose.

Game: Starting number is 9. Catch 5!

Player: playing +3

Game: You played +3, now at 12. Catch 5!

Player: playing -1

Game: You played -1, now at 11. Catch 5!

Player: playing -3

Game: You played -3 with your last move. Now at 8. You lost!

We didn’t manage to get to number 5 within three turns, which means we have lost the game. But how is our machine going to find out how to win the game if we don’t explicitly teach it how to play?

A Q-Table to the rescue

As we can see our game isn’t that complex and we may wonder what’s the point of such a silly toy task. It turns out that a simple task like this makes it very easy for us to explore the topic and enables us to visualize the states and their possible actions in simple graphics.

Notice that the 5 is a special state here as it is the final winning state from which no further action can be done.

Catch 5 game states

Let’s ignore the fact that our defined rules would allow to also reach numbers above 12 or below 1. Even if we allow these states to happen, they don’t change the principles of what we are about to learn.

Our main takeway for now should be, that we can think of our game as a defined space of states where we can take six possible actions from every single state.

The thing that we want our machine to learn is a mapping from every possible state to its best possible action. Let’s assume the best strategy to win this game would be to always move with the biggest possible step towards the number 5. We could visualize the entire state space and their best actions as seen in the following animation.

Catch 5 game states

For instance, starting at 12, the best move we could do is to play -3 which takes us to 9, from where we play -3 again, which takes us to 6, from where we play -1 and win the game in three turns.

Obviously, our strategy isn’t the only one to win the game. Given a starting point of 9 for instance, we could play -3 followed by -1 or we could play -2 followed by -2 or even -1 followed by -3. Each of these pairs of actions would make us win the game in two turns. That is perfectly fine, in fact, we don’t know yet what kind of policy our machine will develop to figure out how to win this game.

The most important learning up to this point should be that our machine can develop a mapping of states to rated actions. Such a mapping is called a Q-Table.

For instance, looking at state 9 we can easily imagine that the machine develops a strong sympathy towards playing -3 indicated in red with the other possible actions having a less strong indication of being the best action.

Catch 5 game states

The complete Q-Table contains all possible states and their rated possible actions. Once the machine has developed such a mapping, it can simply lookup the current state in the table and perform the action with the highest rating. It repeats this process for each turn until it wins the game.

Obviously, this approach does only work if the entire state space is small enough to be represented as such a simple table. But let’s not get ahead of ourselves.

Ok, so we know we want our machine to develop such a Q-Table. But how does that work?

Creating the environment, the game

As we’ve spoiled in the beginning, Reinforcement Learning is about machines acting in an environment and receiving positive or negative rewards to eventually learn a policy to solve a task.

Let’s begin by creating the environment - the game - that our machine will be playing.

The game is already implemented in the demo above. Notice that the interactive demo is written in TypeScript and runs on stackblitz.com. For the rest of the article and the follow-up articles, we’ll be using Python since it has a great ecosystem for Machine Learning. For demos we’ll be using machinelabs.ai which is the perfect platform for these kind of tasks.

Rest assured, the code is equally simple no matter whether we’ll be using TypeScript or Python!

classGame():def__init__(self):self.reset()defreset(self):self.current_number=random.randrange(1,12)if(self.current_number==5):self.reset()self.turns=0defhas_lost(self):returnself.turns>=3andself.current_number!=5defhas_won(self):returnself.turns<=3andself.current_number==5defis_active(self):returnnotself.has_lost()andnotself.has_won()defplay(self,action:Action):if(self.turns>=3):raiseException('Max number of turns reached. Call reset.')self.turns+=1;self.current_number+=int(action)returnself.has_won()

There are really only two basic requirements that we have for the API of the game:

  • A play method that takes an action and applies it to the current state. This is the most important API as it will control the game and move us from one state to another.

  • has_won()/has_lost() methods to figure out if we have won or lost the game. These APIs are important to gather feedback to learn from.

We’ve also added some other APIs for convenience but the listed APIs are really the only crucial ones for our mission.

Building the agent

There are multiple actors in Reinforcement Learning. We’ve already build a simple game, which serves as the environment, that we want our machine to act in.

In order to play the game we need an agent that will perform actions in the game with the intention to figure out how to win the game over time.

We’ll start with something really simple and implement two methods, namely play and play_and_train. The play method basically lets the agent play the game for a given number of times in a mode where we can follow the visual output.

# creating random actionsdefget_action(self):returnrandom.randrange(0,6)# mapping actions (0, 1, 2, 3, 4, 5) to answers (3, 2, 1, -1 , -2, -3)defaction_to_answer(self,action):returnactionMap[action]defplay(self,num_times):fornb_gameinrange(1,num_times+1):self.game.reset()print('Starting game #{nb_game}'.format(nb_game=nb_game))while(self.game.is_active()):print('Current number is {current_number}'.format(current_number=self.game.current_number))action=self.get_action()human_readable_answer=self.action_to_answer(action)print('Playing {answer}'.format(answer=human_readable_answer))self.game.play(human_readable_answer)if(self.game.has_won()):print('You won!')if(self.game.has_lost()):print('You lost')print('##############################################')

As you can see, there’s no rocket sience behind it. A simple loop for the number of times we want our agent to play and an inner loop for the game itself to make turns as long as the game isn’t either won or lost.

Also notice that we are mapping the human readable answers (+3, +2..-2, -3) to zero based values (0, 1..4, 5) so that we can easily address them as indexes in an array to make our life much easier.

Since our agent is just making random moves, we should’t expect super powers either. Here’s some output from it playing randomly. It even won game #4 by accident. Hooray! For this simple game though we’d really want our agent to win 100% of the games!

Starting game #1
Current number is 7
Playing 1
Current number is 8
Playing 2
Current number is 10
Playing 3
You lost
##############################################
Starting game #2
Current number is 10
Playing 1
Current number is 11
Playing 1
Current number is 12
Playing -2
You lost
##############################################
Starting game #3
Current number is 1
Playing 3
Current number is 4
Playing -2
Current number is 2
Playing -2
You lost
##############################################
Starting game #4
Current number is 7
Playing -2
You won!
##############################################

Ok, great, feel free to checkout this embedded demo to familiarize yourself with the code at this point.

We’ll keep the play method to have an API that we can call that gives us visual feedback when the agent plays the game.

As we mentioned we’ll also implement a play_and_train method which will play in a headless mode, meaning we won’t be able to follow along the moves. Instead, we’ll get live metrics to follow along how the actual training is working out. For instance, we’d like to count the number of won or lost games.

Let’s take a look how play_and_train is implemented.

defplay_and_train(self):stats=TrainStats()forepochinrange(1,self.config.nb_epoch+1):self.game.reset()stats.epoch=epochwhile(self.game.is_active()):state=self.game.current_numberaction=self.get_action()human_readable_answer=self.action_to_answer(action)self.game.play(human_readable_answer)reward=self.get_reward()next_state=self.game.current_numberfinal=notself.game.is_active()self.train(state,action,reward,next_state,final)if(self.game.has_won()):stats.nb_wins+=1if(self.game.has_lost()):stats.nb_losses+=1stats.p_wins=100/epoch*stats.nb_winsstats.p_loss=100/epoch*stats.nb_lossesif(epoch%self.config.print_every_n_epoch==0):self.print_epoch_stats(stats)

It’s really not that much different. Instead of printing out each and every move, we’ll collect stats to print out at a given frequency. Also notice that we slightly changed the wording: From now on, we’ll be calling the number of times that our agent plays the game epochs which is the general term for a complete training cycle.

Collecting the precious bits

The eagle-eyed reader may have spotted that there are in fact some subtle changes that are quite important. We collect five important variables that we are passing to a new train method on our agent.

Let’s take a closer look at what these things are:

  1. The state is just the current number of the game before we take our action. For instance, this may be the number 9.

  2. The action is simply the move that our agent performed on the state. For instance, playing -2 is an action in our game. Notice however that we internally represent these actions with the numbers 0 to 5 and just map them to their human readable values (3..-3) when we invoke play on the game.

  3. The next_state is the current number of the game after we took our action. If the state was 9 and the action was -2 the next_state will be 7.

  4. The final variable is True when the game is either won or lost and False when the game is still active.

  5. The most important variable that we haven’t yet talked about is the reward. At the end of the day, this is what enables our agent to learn at all. We’ll get the reward by calling self.get_reward() and we’ll take a look at the implementation in the next section.

The reward function

As mentioned, the reward function is one of the most important things to design in a Reinforcement Learning system. It will heavily influence the policy that our agent will learn.

It’s out of the scope of this article to discuss this in detail. Fortunately designing the rewards should be pretty straight forward and easy to follow for our simple task.

Remember that this is the function we call after our agent performed an action.

defget_reward(self):ifself.game.has_won():return1elifself.game.has_lost():return-1else:return-0.1

As we can see, we return a positive reward of 1 when our agent won the game, a negative reward of -1 when it lost the game and a negative reward of -0.1 for every action that didn’t directly lead to winning or losing the game. In other words, every action is penalized with a slightly negative reward of -0.1. This makes sense if we keep in mind that it is our goal to win the game in a maximum of three turns. So even if we consider that we have to make at least one turn per game, turns can be considered costly overall which is essentially what we price in here.

Updating the Q-Table

Ok, so let’s see how we can build up this Q-Table that we talked about before. Remember we are calling self.train(state, action, reward, next_state, final) and by now we should have a clear understanding what each parameter represents.

Before we move on, let’s create a new instance member on our agent called qtable. We’ll initialize it as an empty hash map. Remember that we said the Q-Table basically maps states to rated actions. Naturally these things can well be represented as hash maps.

self.qtable={}

We will also create a new method ensure_qtable_entry which takes care of creating entries in our qtable in case they don’t exist yet.

defensure_qtable_entry(self,state):ifstatenotinself.qtable:self.qtable[state]=np.zeros(6)

Notice that the key is the state itself and the value is a numpy array with six entries initialized to 0, each representing one of our six possible actions. If the term numpy array is new to you, just think of an array with a super powerful API.

With that in place, let’s unveil what happens inside the train method.

deftrain(self,state,action,reward,next_state,final):self.ensure_qtable_entry(state)self.ensure_qtable_entry(next_state)iffinal:q_value=rewardelse:next_state_actions=self.qtable[next_state]next_state_max=np.amax(next_state_actions)q_value=reward+self.config.discount_factor*next_state_maxself.qtable[state][action]=q_value

The first two lines are just to ensure we have entries in the qtable for the values at state and next_state as we are about to work with them.

The real hot sauce is in the very last line of the method. Here we can clearly see that we are mutating the rating of the action that we took on the state. This makes perfect sense because, again, we want to build up a map of rated actions for each state so that the agent can lookup the best possible action for each state when it plays the game. We call this value the q_value.

This brings us to the question, how do we calculate the q_value? This is in fact a difficult question to answer because if we think back about our rewards we remember that only the final winning or losing state gives pretty clear rewards of +1 or -1. All the other moves that don’t immediately cause the game to be won or lost, such as playing -3 on a 9, just yield us a negative reward of -0.1.

We somehow have to find a way to consider that when we calculate the q_value. Remember that we want -3 to become an action with a high q-value for state 9. On the other hand we don’t want -3 to get a strong q-value for state 6 as clearly playing -1 would be better and make us win the game from here.

In other words, what we need is a formular that not only takes the immediate reward into account but also the rewards that are yet to follow if we choose the given action from here. We call that the discounted future reward.

The formular for that is hiding in this innocently looking if / else block.

iffinal:q_value=rewardelse:next_state_actions=self.qtable[next_state]next_state_max=np.amax(next_state_actions)q_value=reward+self.config.discount_factor*next_state_max

It says that if we reached a final state (won or lost) the q_value should simply be the reward. This makes a lot of sense because there is no future reward to expect from subsequent actions simply because there are no further actions possible from here.

If, however, we aren’t in a final state we do the following:

  1. We get the the highest q-value for the next_state. Remember the next_state is the state that we shifted to as we applied the action to the state. In other words, if the state was 9 and the action was -3 the next_state is 6. We don’t care which action of the next_state has the highest q-value, we simply want get the value to use it in our formular.

  2. We calculate the new q_value for the action of the state as the reward plus the highest q-value of the next_state multiplied by some mysterious discount_factor. Let’s ignore the discount_factor for a moment and just think of it as being set to 1. Replacing our variables with concrete numbers this may boild down to q_value = -0.1 + 1 * 1.

By now, you may be wondering: “But how does that achieve the learning? There’s no guarantee that the q-value for the next_state makes any sense?!”

You’re kinda right, the q-values won’t be perfect from the beginning. In fact, they may start out completely wrong. What this formular achieves though is that the values approximate and get more and more accurate with every iteration.

Getting back to the discount_factor, this thing is less scary than we may think. The discount_factor should be set between 0 and 1 and will influence how much we care about future rewards. A discount_factor of 0 means that we eleminate the righthand side of our formular entirely and don’t price in any future rewards. On the other hand, a discount_factor of 1 means that we strongly care about future rewards. A value of 0.9 is usually a good start but since our task is completely deterministic we can also set it to 1.

The nice thing about using such a terrible simple toy task is that we can make this process perfectly visible. We can simply print out the Q-Table at different stages of the learning.

print(self.qtable)

This is what the Q-Table looks like after our agent played the game 10 times.

{# actions go from +3, +2, + 1, -1, -2, -30:array([0.,0.,0.,-0.1,0.,-1.]),1:array([0.,0.,0.,-0.1,0.,0.]),2:array([0.,-0.1,-0.1,0.,-1.,0.]),3:array([0.,1.,0.,-0.1,0.,-0.1]),4:array([0.,0.673289,0.,0.89,0.,0.]),5:array([0.,0.,0.,0.,0.,0.]),# it is in favor of playing -2 from here. Stupid machine!6:array([-1.,0.,0.,0.,0.7811,-0.1]),7:array([-1.,-0.1,0.,0.,0.,0.]),9:array([0.,-0.1,-0.1,0.,0.,0.]),10:array([0.,-0.1,-0.1,0.,0.,-0.1]),11:array([0.,-1.,0.,0.,0.,0.]),12:array([0.,-0.1,0.,0.,0.,0.]),13:array([0.,0.,0.,0.,0.,0.]),14:array([0.,0.,0.,0.,0.,-1.]),-2:array([0.,0.,0.,0.,0.,0.]),-3:array([0.,0.,0.,0.,0.,0.]),-1:array([0.,0.,0.,-1.,0.,0.])}

Each line represents a state with the right-hand side being the array of q-values for each action starting at 3 and ending at -3. Notice how the machine “thinks” playing a -2 on a six would be the best move. We need to give it some more training cycles!

And after 100 games the Q-Table looks like this. By now, the agent has figured out how to play perfectly. Also notice that the entire Q-Table grow a bit as it figured out how to get to exotic states such as 20 or -7.

{# actions go from +3, +2, + 1, -1, -2, -30:array([-1.,0.89,0.7811,0.7811,-1.,-1.09]),1:array([0.89,0.89,0.89,0.7811,-1.,-1.]),2:array([1.,0.89,0.89,-1.,-1.,0.7811]),3:array([0.89,1.,0.89,0.89,-1.,-1.]),4:array([0.89,0.89,1.,0.89,0.89,0.7811]),5:array([0.,0.,0.,0.,0.,0.]),# it figured out to play -1 from here6:array([-1.,-1.,0.89,1.,0.89,0.89]),7:array([-1.,0.7811,0.89,0.89,1.,0.89]),8:array([0.7811,0.7811,0.7811,0.89,0.89,1.]),9:array([0.673289,-1.,-1.,0.89,0.89,0.89]),10:array([0.673289,-1.,-1.,-1.,-1.,0.89]),11:array([-0.199,-1.,0.56655611,0.7811,0.7811,0.89]),12:array([-1.09,0.673289,-1.,-1.,0.7811,-1.]),13:array([-1.09,-1.09,-1.,-1.,-1.,0.7811]),14:array([-0.1,-1.09,-1.09,-1.,-1.,-1.]),15:array([-1.,-1.,-1.,-1.,-1.,-1.]),16:array([-1.,-1.,-1.,-1.,-1.,-1.]),17:array([-1.,-1.,-1.,-1.,-1.,-1.]),18:array([0.,0.,0.,0.,0.,0.]),19:array([0.,0.,0.,0.,0.,0.]),20:array([0.,0.,0.,0.,0.,0.]),-2:array([0.7811,0.7811,-1.09,-1.,-1.09,-1.]),-8:array([0.,0.,0.,0.,0.,0.]),-7:array([0.,0.,0.,0.,0.,0.]),-6:array([0.,0.,0.,0.,0.,0.]),-5:array([-1.,-1.,-1.,-1.,-1.,-1.]),-4:array([-1.,-1.,-1.,-1.,-1.,-1.]),-3:array([-1.,-1.,-1.,-1.,-1.,-1.]),-1:array([0.89,-1.,-1.,-1.,-1.,-1.09])}

Notice that the Q-Table righfully favors playing -1 on a 6 now. All the other moves seem to make sense as well.

Playing with the trained agent

It’s really cool and handy that we are able to validate the learning simply by looking at the Q-Table. But of course we don’t want to stop here. We want to truly measure wether our agent wins every single game.

Notice that our agent explored the game entirely by making random moves so far. This is only possible because the entire state space of our task is very small. For real world tasks this strategy wouldn’t take us very far and we have to refine the approach, but that’s a story for another post.

Still, to measure the performance of our agent, we have to be able to control wether it chooses actions randomly or based on Q-Table lookups.

A simple condition would do the trick but we can do a litle better and introduce a randomness_rate to make fine grained adjustments so that, for instance, 70 % percent of the actions are choosen randomly and 30 % based on Q-Table lookups. This will already pave the way for other optimizations that we’ll be applying in a future post.

We just have to apply a tiny refactoring to our get_action method to pass in the state and then return an action randomly or based on a Q-Table lookup depending on the randomness_rate.

defget_action(self,state):ifnotself.should_go_random()andstateinself.qtable:returnself.predict_action(state)returnself.get_random_action()defshould_go_random(self):returnnp.random.rand()<=self.randomness_ratedefget_random_action(self):returnrandom.randrange(0,6)defpredict_action(self,state):returnnp.argmax(self.qtable[state])

A randomness_rate of 0 means that all actions should be based on Q-Table lookups (unless the Q-Table is lacking the entry) whereas a value of 1 means that all actions should be choosen randomly. We can choose any value in between 0 and 1 such as 0.3 so that 30 % of the actions are picked randomly.

With that in place we can first perform 100 epochs of training and then have the trained agent play 1000 games.

config=AgentConfig()config.nb_epoch=100agent=Agent(config)agent.play_and_train()#play 1000 games on the trained agentconfig.nb_epoch=1000agent.randomness_rate=0agent.play_and_train()

We can see that it really wins every single game out of 1000.

Epoch:1000Wins:1000(100.00%)Losses:0(0.00%)

We can also see that it’s not always going for the straight path that we would expect it to take.

Startinggame#2Currentnumberis10Playing-1Currentnumberis9Playing-1Currentnumberis8Playing-3Youwon!##############################################

However this is just becaue it was trained on 100 randomly played games. We could increase the number of games the agent performs for the training or fine tune our training strategy to fix that!

You can check out the final code of the agent playing perfectly in this embedded lab.

Play with the code, fork it and try solving other challenges!

Wrapping up

Phew! This was quite a long post. Congrats if you made it this far. We hope you had fun exploring the exciting field of Reinforement Learning. As mentioned in the beginning, this is part one in a series of articles on Reinforcement Learning. Stay tuned!

Advanced caching with RxJS

$
0
0

When building web applications, performance should always be a top priority. There are many things we can do to speed up our Angular applications like tree-shaking, AoT (ahead-of-time compilation), lazy loading modules or caching. To get an overview of practices that will help you boost the performance of your Angular applications, we highly recommend you to check out the Angular Performance Checklist by Minko Gechev. In this post we focus on caching.

In fact, caching is one of the most efficient ways to improve the experience of our site, especially when our users are on bandwidth restricted devices or slow networks.

There are several ways to cache data or assets. Static assets are most commenly cached with the standard browser cache or Service Workers. While Service Workers can also cache API requests, they are typically more useful for caching resources like images, HTML, JS or CSS files. To cache application data we usually use custom mechanisms.

No matter what mechanism we use, a cache generally improves the responsiveness of our application, decreases network costs and has the advantage that content becomes available during network interruptions. In other words, when the content is cached closer to the consumer, say on the client side, requests don’t cause additional network activity and cached data can be retrieved much faster because we save on an entire network round trip.

In this post we’ll develop an advanced caching mechanism with RxJS and the tools provided by Angular.

Motivation

Every now and then there’s this question popping up how to cache data in an Angular application that makes excessive use of Observables. Most people have a good understanding on how to cache data with Promises but feel overwhelmed when it comes to functional reactive programming, due to its complexity (large API), fundamental shift in mindset (from imperative to declarative) and the multitude of concepts. Hence, it can be hard to actually translate an existing caching mechanism based on Promises to Observables, especially if you want that mechanism to be a little bit more advanced.

In an Angular application, we typically perform HTTP requests through the HttpClient that comes with the HttpClientModule. All of its APIs are Observable-based meaning that methods like get, post, put or delete return an Observable. Because Observables are lazy by nature the request is only made when we call subscribe. However, calling subscribe multiple times on the same Observable will cause the source Observable to be re-created over and over again and, hence, perform a request on each subscription. We call this cold Observables.

If you are completely new to this, we have written an article on Cold vs Hot Observables.

This behavior can make it tricky to implement a caching mechanism with Observables. Simple approaches often require a fair amount of boilerplate and we probably end up bypassing RxJS, which works, but is not the recommended way if we want to harness the power of Observables. Literally speaking, we don’t wanna drive a Ferrari with a scooter engine, right?

The Requirements

Before we dive into code, let’s start to define the requirements for our advanced caching mechanism.

We want to build an application called World of Jokes. It’s a simple app that randomly shows jokes for a given category. To keep it simple and focused, there’s only one category.

This app has three components: AppComponent, DashboardComponent and JokeListComponent.

The AppComponent is our entry point and renders a toolbar as well as a <router-outlet> that is filled based on the current router state.

The DashboardComponent simply shows a list of categories. From here, we can navigate to the JokeListComponent which then renders a list of jokes onto the screen.

The jokes themselves are fetched from a server using Angular’s HttpClient service. To keep the component’s responsibility focused and separate the concerns, we want to create a JokeService that takes care of requesting the data. The component can then simply inject the service and access the data through its public APIs.

All of the above is just our application’s architecture and there’s no caching involved yet.

When navigating from the dashboard to the list view, we prefer to request the data from a cache rather than requesting it from the server every time. The underlying data of this cache would update itself every 10 seconds.

Of course, polling for new data every 10 seconds isn’t a solid strategy for a production app where we would rather use a more sophisticated approach to update the cache (e.g. web socket push updates). But we’ll try to keep things simple here to focus on the caching aspect.

In any case we’d receive some sort of update notification. For our application we want the data in the UI (JokeListComponent) to not automatically update when the cache updates but rather waits for the user to enforce the UI update. Why? Imagine a user may be reading one of the jokes and then all of a sudden it’s gone because the data is updated automatically. That would be super annoying and a bad user experience. Therefore, our users receive notifications whenever new data is available.

To make it even more fun, we want the user to be able to force the cache to update. This is different from solely updating the UI because forcing an update means to freshly request the data from the server, update the cache and then also update the UI accordingly.

Let’s summarize what we want to build:

  • Our app has two components where navigating from component A to B should prefer requesting B’s data from a cache rather than requesting it from the server every time
  • Cache is updated every 10 seconds
  • Data in the UI is not automatically updated and requires the user to enforce an update
  • User can force an update that will cause a request to actually update the cache and the UI

Here’s a preview of what we are going to build:

app preview

Implementing a basic cache

Let’s start simple and work our way up to the final and fully-fledged solution.

The first step is to create a new service.

Next, we’ll add two interfaces, one that describes the shape of a Joke and the other is used to strongly type the response of our HTTP request. This makes TypeScript happy but most importantly more convenient and obvious to work with.

exportinterfaceJoke{id:number;joke:string;categories:Array<string>;}exportinterfaceJokeResponse{type:string;value:Array<Joke>;}

Now let’s implement the JokeService. We don’t want to reveal the implementation detail of whether the data was served from cache or freshly requested from the server, hence we simply expose a property jokes returning an Observable that captures a list of jokes.

In order to perform HTTP requests, we need to make sure to inject the HttpClient service in the constructor of our service.

Here’s the shell for the JokeService:

import{Injectable}from'@angular/core';import{HttpClient}from'@angular/common/http';@Injectable()exportclassJokeService{constructor(privatehttp:HttpClient){}getjokes(){...}}

Next, we implement a private method requestJokes() which uses the HttpClient to perform a GET request to retrieve a list of jokes.

import{map}from'rxjs/operators';@Injectable()exportclassJokeService{constructor(privatehttp:HttpClient){}getjokes(){...}privaterequestJokes(){returnthis.http.get<JokeResponse>(API_ENDPOINT).pipe(map(response=>response.value));}}

With that in place, we have everything we need to implement the jokes getter method.

One naive approach would be to simply return this.requestJokes(), but that doesn’t do the trick. We know from the beginning that all methods exposed by the HttpClient, for instance get, return cold Observables. This means that the whole data stream is re-emitted for each subscriber causing an overhead of HTTP requests. After all, the idea behind a cache is to speed up the load time of our application and limit the amount of network requests to a minimum.

Instead we want to make our stream hot. Not only that, but every new subscriber should receive the latest cached value. It turns out that there’s a very convenient operator called shareReplay. This operator returns an Observable that shares a single subscription to the underlying source, which is the Observable returned from this.requestJokes().

In addition, shareReplay accepts an optional parameter bufferSize that is really handy in our case. The bufferSize determines the maximum element count of the replay buffer, that is the number of elements that are cached and replayed for every subscriber. For our scenario we only want to replay the most recent value and, hence, set the bufferSize to one (1).

Let’s look at the code and use what we have just learned:

import{Observable}from'rxjs/Observable';import{shareReplay,map}from'rxjs/operators';constAPI_ENDPOINT='https://api.icndb.com/jokes/random/5?limitTo=[nerdy]';constCACHE_SIZE=1;@Injectable()exportclassJokeService{privatecache$:Observable<Array<Joke>>;constructor(privatehttp:HttpClient){}getjokes(){if(!this.cache$){this.cache$=this.requestJokes().pipe(shareReplay(CACHE_SIZE));}returnthis.cache$;}privaterequestJokes(){returnthis.http.get<JokeResponse>(API_ENDPOINT).pipe(map(response=>response.value));}}

Ok, we already talked about most of what we see above. But wait, what’s about the private cache$ property and if statement inside the getter? The answer to this is quite simple. If we returned this.requestJokes().pipe(shareReplay(CACHE_SIZE)) directly then every subscriber creates a new cache instance. However, we want to share a single instance across all subscribers. Therefore, we keep the instance in a private property cache$ and initialize it as soon as the getter was called the first time. All subsequent consumers will receive the shared instance without re-creating the cache every time.

Let’s look at a more visual representation of what we’ve just implemented:

sequence diagram for our simple cache mechanism

Above we can see a sequence diagram that depicts the objects involved in our scenario, that is requesting a list of jokes, and the sequences of messages exchanged between the objects. Let’s break it down to understand what’s going on here.

We start out on the dashboard from where we navigate to the list component.

After the component was initialized and Angular calls the ngOnInit life cycle hook, we request the list of jokes by calling the getter function jokes exposed by the JokeService. Since this is the first time we ask for the data, the cache itself is empty and not yet initialized, meaning JokeService.cache$ is undefined. Internally we call requestJokes(). This will give us an Observable that emits the data from the server. At the same time we apply the shareReplay operator to get the desired behavior.

The shareReplay operator automatically creates a ReplaySubject between the original source and all future subscribers. As soon as the number of subscribers goes from zero to one it will connect the Subject to the underlying source Observable and broadcast all its values. All future subscribers will be connected to that in-between Subject, so that effectively there’s just one subscription to the underlying cold Observable. This is called multicasting and defines the foundation for our simple cache.

Once the data comes back from the server it will be cached.

Note that the Cache is a standalone object in the sequence diagram and is supposed to illustrate the ReplaySubject that is created in between the consumer (subscribers) and the underlying source (HTTP request).

The next time we request the data for the list component, our cache will replay the most recent value and send that to the consumer. There’s no additional HTTP call involved.

Simple, right?

To rip this really apart, let’s take this one step further and look at how the cache works on an Observable level. For this we use a marble diagram to visualize how the stream actually works:

marble diagram for our cache

The marble diagram makes it really clear that there’s only one subscription to the underlying Observable and all consumers simply subscribe to the shared Observable, that is the ReplaySubject. We can also see that only the first subscriber triggers the HTTP call and all others get the latest value replayed.

Finally, let’s look at the JokeListComponent and how we can display the data. The first step is to inject the JokeService. After that, inside ngOnInit we initialize a property jokes$ with the value returned by the getter function that is exposed by our service. This will return an Observable of type Array<Joke> and this is exactly what we want.

@Component({...})exportclassJokeListComponentimplementsOnInit{jokes$:Observable<Array<Joke>>;constructor(privatejokeService:JokeService){}ngOnInit(){this.jokes$=this.jokeService.jokes;}...}

Note that we are not imperatively subscribing to jokes$. Instead we use the async pipe in the template because it turns out that this pipe is full of little wonders. Curious? Check out this article that unravels three things you didn’t know about the AsyncPipe.

<mat-card*ngFor="let joke of jokes$ | async">...</mat-card>

Cool! Here’s our simple cache in action. To verify if the request is only made once, open up Chrome’s DevTools, click on the Network tab and then select XHR. Start out on the dashboard, go to the list view and then navigate back and forth.

Automatic updates

So far we have built a simple caching mechanism in a few lines of code. In fact, most of the heavy lifting is done by the shareReplay operator which takes care of caching and replaying the most recent value(s).

This works perfectly fine but the data is never actually updated in the background. What if the data is likely to change every few minutes? We certainly don’t want to force the user to reload the entire page just to get the latest data from the server.

Wouldn’t it be cool if our cache is updated every 10 seconds in the background? Totally! As a user we don’t have to reload the page and if the data has changed the UI will update accordingly. Again, in a real-world application we would most probably not even use polling but instead have the server push notifications. For our little demo app a refresh interval of 10 seconds is just fine.

The implementation is fairly easy. In a nutshell, we want to create an Observable that emits a sequence of values spaced by a given time interval, or simply said, we want to produce a value every X milliseconds. For that we have several options.

The first option is to use interval. This operator takes an optional parameter period that defines the time between each emission. Consider the following example:

import{interval}from'rxjs/observable/interval';interval(10000).subscribe(console.log);

Here we set up an Observable that emits an infinite sequence of integers where each value is emitted every 10 seconds. That also means that the first value is somewhat delayed by the given interval. To better demonstrate the behavior, let’s take a look at the marble diagram for interval.

interval operator

Yep, as expected. The first value is “delayed” and this is not what we want. Why? Because if we come from the dashboard and navigate to the list component to read some funny jokes, we’d have to wait for 10 seconds before the data is requested from the server and rendered onto the screen.

We could fix this by introducing another operator called startWith(value) which would emit the given value first, as an initial value. But we can do better!

What if I told you that there’s an operator that emits a sequence of values after a given duration (initial delay) and then after each period (regular interval)? Meet timer.

Visualization time!

timer operator

Cool, but does that really solve our problem? Yep it does. If we set the initial delay to zero (0) and set the period to 10 seconds, we end up with the same behavior as if we used interval(10000).pipe(startWith(0)) but only with a single operator.

Let’s take that and plug it into our exisiting caching mechanism.

We have to set up a timer and for every tick we want to make an HTTP request to fetch new data from the server. That is, for every tick we need to switchMap to an Observable that, on subscription, fetches a new list of jokes. Using switchMap has the positive side effect that we avoid race conditions. That’s due to the nature of this operator because it will unsubscribe from the previously projected Observable and only emit values from the most recently projected Observable.

The rest of our cache remains untouched, meaning that our stream is still multicasted and all subscribers share one underlying source.

Again, the nature of shareReplay will broadcast new values to exisiting subscribers and replay the most recent value to new subscribers.

timer based cache

As we can see in the marble diagram, the timer emits a value every 10 seconds. For every value we switch to an inner Observable that fetches our data. Because we are using switchMap, we avoid race conditions and therefore the consumer only receives the value 1 and 3. The value from the second inner Observable is “skipped” because we are already unsubscribed when the value arrives.

Let’s apply our learnings and update the JokeService accordingly.

import{timer}from'rxjs/observable/timer';import{switchMap,shareReplay}from'rxjs/operators';constREFRESH_INTERVAL=10000;@Injectable()exportclassJokeService{privatecache$:Observable<Array<Joke>>;constructor(privatehttp:HttpClient){}getjokes(){if(!this.cache$){// Set up timer that ticks every X millisecondsconsttimer$=timer(0,REFRESH_INTERVAL);// For each tick make an http request to fetch new datathis.cache$=timer$.pipe(switchMap(_=>this.requestJokes()),shareReplay(CACHE_SIZE));}returnthis.cache$;}...}

Awesome! Wanna try it out yourself? Go ahead and play with the following live demo. From the dashboard, go to the list component and then watch the magic happening. Give it a few seconds so that you can see the update in action. Remember, the cache is refreshed every 10 seconds, but feel free to fiddle with the REFRESH_INTERVAL.

Sending update notifications

Let’s recap for a moment what we have built so far.

When we request data from our JokeService we always prefer to request that data from a cache rather than requesting it from the server every time. The underlying data of this cache is refreshed every 10 seconds and when this happens, the data is propagated to the component causing the UI to update automatically.

That’s unfortunate. Imagine we’re a user that is reading one of the jokes and all of the sudden it’s gone because the UI is updated automatically. That’s super annoying and a bad user experience.

Therefore, our users should rather receive notifications when there’s new data available. In other words, we want the user to enforce the UI update.

It turns out that we don’t have to touch our service in order to implement this. The logic is quite simple. After all, our service should not worry about sending notifications and the view should be in charge when and how to update the data on the screen.

First, we have to get an initial value to show something to the user, because otherwise the screen will be blank until the cache was updated the first time. We’ll see why in just a moment. Setting up a stream for the inital value is as easy as calling the getter function. Additionally, since we are only interested in the very first value we can use the take operator.

To make this logic reusable we create a helper methode getDataOnce().

import{take}from'rxjs/operators';@Component({...})exportclassJokeListComponentimplementsOnInit{...ngOnInit(){constinitialJokes$=this.getDataOnce();...}getDataOnce(){returnthis.jokeService.jokes.pipe(take(1));}...}

From our requirements we know that we only want to update the UI when the user really enforces an update rather than reflecting the change automatically. How does the user enforce an update you ask? This happens when we click on a button in the UI that says “Update”. This button is shown together with the notification. For now, let’s not worry about the notification and instead focus on the logic that updates the UI when we click that button.

To make this work, we need a way to create an Observable from DOM events, specifically from button clicks. There are several ways but a very common way is to use a Subject as a bridge between the template and the view logic that lives in the component class. In a nutshell, a Subject is a type that implements both Observer and Observable types. Observables define the data flow and produce the data while Observers can subscribe to Observables and receive the data.

The good thing about the Subject here is that we can simply use an event binding in the template and then call next when the event is triggered. This will cause the specified value to be broadcasted to all Observers that are listening for values. Note that we can also omit the value if the Subject is of type void. In fact, this is true for our case.

Let’s go ahead and instantiate a new Subject.

import{Subject}from'rxjs/Subject';@Component({...})exportclassJokeListComponentimplementsOnInit{update$=newSubject<void>();...}

Now we can go ahead and wire this up in the template.

<divclass="notification"><span>There's new data available. Click to reload the data.</span><buttonmat-raised-buttoncolor="accent"(click)="update$.next()"><divclass="flex-row"><mat-icon>cached</mat-icon>
      UPDATE
    </div></button></div>

See how we use the event binding syntax to capture the click event on the <button>? When we click on the button we simply propagate a ghost value causing all active Observers to be notified. We call it ghost value because we are not actually passing in any value, or at least a value of type void.

Another way would be to use the @ViewChild() decorator in combination with the fromEvent operator from RxJS. However, this requires us to “mess” with the DOM and query an HTML element from the view. With a Subject we are actually just bridging the two sides and don’t really touch the DOM at all except the event binding we are adding to the button.

Alright, with the view being setup we can now switch to the logic that takes care of updating the UI.

So what does it mean to update the UI? Well, the cache is updated in the background automatically and we want to render the most recent value from the cache when we click on that button, right? This means that our source stream in this case is the Subject. Every time a value is broadcasted on update$ we want to map this value to an Observable that gives us the latest cached value. In other words, we are dealing with a so-called Higher Order Observable, an Observable that emits Observables.

From before we should know that there’s switchMap that solves exactly this problem. This time we’ll use mergeMap instead. This operator behaves very similar to switchMap with the difference that it does not unsubscribe from the previously projected inner Observable and simply merges the inner emissions in the output Observable.

In fact, when requesting the most recent value from the cache, the HTTP request is already done and the cache was successfully updated. Therefore, we don’t really face the problem of race-conditions here. Though it seems to be asynchronous, it’s actually somewhat synchronous because the value will be emitted in the same tick.

import{Subject}from'rxjs/Subject';import{mergeMap}from'rxjs/operators';@Component({...})exportclassJokeListComponentimplementsOnInit{update$=newSubject<void>();...ngOnInit(){...constupdates$=this.update$.pipe(mergeMap(()=>this.getDataOnce()));...}...}

Sweet! For every “update” we request the latest value from the cache using our helper method we implemented earlier.

From here, it’s only a small step to come up with the stream for the jokes that are rendered onto the screen. All we have to do is to merge the initial list of jokes with our update$ stream.

import{Observable}from'rxjs/Observable';import{Subject}from'rxjs/Subject';import{merge}from'rxjs/observable/merge';import{mergeMap}from'rxjs/operators';@Component({...})exportclassJokeListComponentimplementsOnInit{jokes$:Observable<Array<Joke>>;update$=newSubject<void>();...ngOnInit(){constinitialJokes$=this.getDataOnce();constupdates$=this.update$.pipe(mergeMap(()=>this.getDataOnce()));this.jokes$=merge(initialJokes$,updates$);...}...}

It’s important that we use our helper method getDataOnce() to map each update event to the latest cached value. If we recall, it uses take(1) internally which will take the first value and then complete the stream. This is crucial because otherwise we’d end up with an on-going stream or live connection to the cache. In this case we would basically break our logic of enforcing a UI update only by clicking the “Update” button.

Also, because the underlying cache is multicasted, it’s totally safe to always re-subscribe to the cache to get the latest value.

Before we continue with the notification stream, let’s stop for a moment and visualize what we just implemented as a marble diagram.

jokes

As we can see in the diagram above, initialJokes$ is crucial because otherwise we’d only see something on the screen when we click “Update”. While the data is already updated in the background every 10 seconds, there’s no way we can press this button. That’s because the button is part of the notification and we never really show it to the user.

Let’s fill this gap and implement the missing functionality to complete the puzzle.

For that, we have to create an Observable that is responsible for showing or hiding the notification. Essentially, we need a stream that either emits true or false. We want the value to be true when there’s an update, and false when the user clicks on the “Update” button.

In addition we want to skip the first (initial) value emitted by our cache because it’s not really a refresh.

If we think in terms of streams, we can break this up into multiple streams and merge them together to turn them into a single Observable. The final stream then has the desired behavior to show or hide notifications.

Enough theory! Here’s the code:

import{Observable}from'rxjs/Observable';import{Subject}from'rxjs/Subject';import{skip,mapTo}from'rxjs/operators';@Component({...})exportclassJokeListComponentimplementsOnInit{showNotification$:Observable<boolean>;update$=newSubject<void>();...ngOnInit(){...constinitialNotifications$=this.jokeService.jokes.pipe(skip(1));constshow$=initialNotifications$.pipe(mapTo(true));consthide$=this.update$.pipe(mapTo(false));this.showNotification$=merge(show$,hide$);}...}

Here, we listen for all values emitted by our cache but skip the first because it’s not a refresh. For every new value on initialNotifications$ we map it to true to show the notification. Once we click the “Update” button in the notification, a value is produced on update$ and we can simply map it to false causing the notification to disappear.

Let’s use showNotification$ inside the template of the JokeListComponent to toggle a class that either shows or hides the notification.

<divclass="notification"[class.visible]="showNotification$|async">
  ...
</div>

Yay! We are getting really close to the final solution. But before we continue, let’s try it out and play around with the live demo. Take your time and go through the code step by step again.

Fetching new data on demand

Awesome! We have come a long way and already implemented a few very cool features for our cache. To finish up this article and take our cache to a whole new level, there’s one thing left for us to do. As a user we want to be able to force an update at any point in time.

It’s not really that complicated but we have to touch both the component and the service to make this work.

Let’s start with our service. What we need is a public facing API that will force the cache to reload the data. Technically speaking, we’ll complete the current cache and set it to null. This means that the next time we request the data from our service we will set up a new cache, fetch the data and store this for future subscribers. It’s not a big deal to create a new cache every time we enforce an update because it will be completed and eventually garbage collected. In fact, this has the positive side effect that we also reset the timer which is absolutely desired. Let’s say we have waited 9 seconds and now click “Fetch new Jokes”. We expect the data to be refreshed, but we don’t to see a notification popping up 1 second later. Instead we want to restart the timer so that when we enforce an update it another 10 seconds to trigger the automatic update.

Another reason for the destroying the cache is that it’s much less complex compared to a mechanism that keeps the cache running all the time. If that’s the case then the cache needs to be aware of whether a reload was enforced or not.

Let’s create a Subject that we use to tell the cache to complete. We’ll leverage takeUntil and pluck it into our cache$ stream. In addition, we implement a public facing API that, internally, sets the cache to null and also broadcasts an event on our Subject.

import{Subject}from'rxjs/Subject';import{timer}from'rxjs/observable/timer';import{switchMap,shareReplay,map,takeUntil}from'rxjs/operators';constREFRESH_INTERVAL=10000;@Injectable()exportclassJokeService{privatereload$=newSubject<void>();...getjokes(){if(!this.cache$){consttimer$=timer(0,REFRESH_INTERVAL);this.cache$=timer$.pipe(switchMap(()=>this.requestJokes()),takeUntil(this.reload$),shareReplay(CACHE_SIZE));}returnthis.cache$;}forceReload(){// Calling next will complete the current cache instancethis.reload$.next();// Setting the cache to null will create a new cache the// next time 'jokes' is calledthis.cache$=null;}...}

This alone doesn’t do much, so let’s go ahead and use that in our JokeListComponent. For this we’ll implement a function forceReload() that is called whenever we click on the button that says “Fetch new Jokes”. Also, we need to create a Subject that is used as an event bus for updating the UI as well as showing the notifications. We’ll see in a moment where this comes into play.

import{Subject}from'rxjs/Subject';@Component({...})exportclassJokeListComponentimplementsOnInit{forceReload$=newSubject<void>();...forceReload(){this.jokeService.forceReload();this.forceReload$.next();}...}

With this in place we can wire up the button in the template of the JokeListComponent to force the cache to reload the data. All we have to do is listen for the click event using Angular’s event binding syntax and call forceReload().

<buttonclass="reload-button"(click)="forceReload()"mat-raised-buttoncolor="accent"><divclass="flex-row"><mat-icon>cached</mat-icon>
    FETCH NEW JOKES
  </div></button>

This already works, but only if we go back to the dashboard and then again to the list view. This is of course not what we want. We want the UI to update immediately when we force the cache to reload the data.

Remeber that we have implemented a stream updates$ that, when we click on “Update”, requests the latest data from our cache? It turns out that we need exactly the same behavior, so we can go ahead and extend this stream. This means we have to merge both update$ and forceReload$, because those two streams are the sources for updating the UI.

import{Subject}from'rxjs/Subject';import{merge}from'rxjs/observable/merge';import{mergeMap}from'rxjs/operators';@Component({...})exportclassJokeListComponentimplementsOnInit{update$=newSubject<void>();forceReload$=newSubject<void>();...ngOnInit(){...constupdates$=merge(this.update$,this.forceReload$).pipe(mergeMap(()=>this.getDataOnce()));...}...}

That was easy, wasn’t it? Yea but we are not done. In fact, we just “broke” our notifications. It all works just fine until we click “Fetch new Jokes”. The data is updated on the screen as well as in our cache, but when we wait 10 seconds there’s no notification popping up. The problem here is that forcing an update will complete the cache instance, meaning we no longer receive values in the component. The notification stream (initialNotifications$) is basically dead. That’s unfortunate, so how can we fix this?

Quite easy! We listen for events on forceReload$ and for every value we switch to a new notification stream. Important here is that we unsubscribe from the previous stream. Does that ring a bell? Sounds a lot like we need switchMap here, doesn’t it?

Let’s get our hands dirty and implement this!

import{Observable}from'rxjs/Observable';import{Subject}from'rxjs/Subject';import{merge}from'rxjs/observable/merge';import{take,switchMap,mergeMap,skip,mapTo}from'rxjs/operators';@Component({...})exportclassJokeListComponentimplementsOnInit{showNotification$:Observable<boolean>;update$=newSubject<void>();forceReload$=newSubject<void>();...ngOnInit(){...constreload$=this.forceReload$.pipe(switchMap(()=>this.getNotifications()));constinitialNotifications$=this.getNotifications();constshow$=merge(initialNotifications$,reload$).pipe(mapTo(true));consthide$=this.update$.pipe(mapTo(false));this.showNotification$=merge(show$,hide$);}getNotifications(){returnthis.jokeService.jokes.pipe(skip(1));}...}

That’s it. Whenever forceReload$ emits a value we unsubscribe from the previous Observable and switch to a new notification stream. Note that there’s a piece of code that we needed twice, that is this.jokeService.jokes.pipe(skip(1)). Instead of repeating ourselves, we created a function getNotifications() that simply returns a stream of jokes but skips the first value. Finally, we merge both initialNotifications$ and reload$ together into one stream called show$. This stream is responsible for showing the notification on the screen. There’s also no need to unsubscribe from initialNotifications$ because this stream completes before the cache is re-created on the next subscription. The rest stays the same.

Puh, we did it. Let’s take a moment and look at a more visual representation of what we just implemented.

notification system

As we can see in the marble diagrams, initialNotifications$ is very important for showing notifications. If we were missing this particular stream then we would only see a notification when we force the cache to update. That said, when we request new data on demand, we have to constantly switch to a new notification stream because the previous (old) Observable will complete and no longer emit values.

That’s it! We’ve made it and implemented a sophisticated caching mechanism using RxJS and the tools provided by Angular. To recap, our service exposes a stream that gives us a list of jokes. The underlying HTTP request is periodically triggered every 10 seconds to update the cache. To improve the user experience, we show a notification so that the user has to enforce an update of the UI. On top of that, we have implemented a way for the user to request new data on demand.

Awesome! Here’s the final solution. Take a few minutes to review the code once again. Try out different scenarios to see if everything works as expected.

Outlook

If you want some homework or brain food for later, here are some thoughts for improvements:

  • Add error handling
  • Refactor the logic from the component into a service to make it reusable

Special Thanks

Special thanks to Kwinten Pisman for helping me with the code. Also, I’d like to thank Ben Lesh and Brian Troncone for giving me valuable feedback and pointing out a few improvements. In addition, big thanks to Christoph Burgdorf for reviewing my article as well as the code.

RxJS Master Class and courseware updates

$
0
0

When we introduced our first workshop a few years back, we knew this was just the first step to creating a sophisticated portfolio of courseware material, that enables us to provide high-quality software training.

Ever since, we’ve added the Angular Master Class to our products and we kept improving, fine-tuning and battle-testing it over the years. We’ve extended the courseware to cover topics like state management with Redux and ngrx as well, and obviously, there’s still so much more to come.

Progressive Web Apps and Server-side Rendering

In fact, today we’re happy to let you know that our Angular Master Class now officially covers Progressive Web Apps with Angular and Server-side rendering with Angular Universal as well!

These are the missing pieces to take your Angular apps to the next level and provide a richer user experience, by making them blazingly fast. Those topics might become part of our upcoming Angular Master Class in Germany and we have a couple of seats left!

Introducing RxJS Master Class

By the title of this post, you might have guessed that this is not everything we have to share today. Many of you might have noticed that Angular embraces functional reactive programming paradigms using RxJS. We see many people struggling with this topic, which is why we decided to create a dedicated class just about that!

The fun thing about this class is that you’ll build the classic video game “Snake” from scratch, using Observables and powerful operators purely with RxJS. This also means that this class is a standalone one and doesn’t go necessarily hand-in-hand with our Angular Master Class.

Also, we might plan to extend our RxJS Master Class with more features for the game so we can explore even more sophisticated scenarios with RxJS! Head over to our official website for more information.

That’s it so far from our side, if you’re interested in public or corporate in-house class, as always, feel free to reach out to us!


The How and Why on using dynamic Angular components inside your custom widgets

$
0
0

In my previous article I showed how you can integrate a third party widget with Angular using as an example a datagrid widget by ag-Grid. Most widgets you’ll find on the web are customizable and ag-Grid is not an exception. In fact, here at ag-Grid we strongly believe that developers should be able to easily extend the default functionality to meet their business requirements. For example, you can provide your custom components to implement a custom cell renderer, cell editor or custom filters.

Pure JavaScript version of ag-Grid is extended by implementing a JavaScript component - a class that implements methods for communication between ag-Grid and the component. For example, all components that implement some kind of UI element must implement getGui method that returns a DOM hierarchy that our JavaScript datagrid will render on the screen.

However, when ag-Grid is used inside Angular, we don’t directly work with DOM. In Angular, we define UI for a component through a template and delegate DOM manipulation to Angular. And this is exactly the possibility we want to provide for someone who wants to customize ag-Grid that is used as an Angular component. We want to allow our users to customize parts of our Angular datagrid by implementing Angular components.

Let me give you an example. A developer uses ag-Grid as an Angular component and wants to implement a requirement to format all numbers in cells according to a user’s locale (EUR). To implement this formatting logic in the pure JavaScript grid version, the developer needs to wrap this logic into a JavaScript class and implement the getGui method that returns the DOM with formatted values. This will be a component that ag-Grid will use for cell rendering, hence the type of a component is defined in the docs as a cell renderer. Here is how it could look:

classNumberCellFormatter{init(params){consttext=params.value.toLocaleString(undefined,{style:'currency',currency:'EUR'});this.eGui=document.createElement('span');this.eGui.innerHTML=text;}getGui(){returnthis.eGui;}}

But when ag-Grid is used as an Angular datagrid, we want developers to define a cell render in Angular way like this:

@Component({selector:'app-number-formatter-cell',template:`<span>{{params.value|currency:'EUR'}}</span>
`})exportclassNumberFormatterComponent{params:any;agInit(params:any):void{this.params=params;}}

Also, as you can see, if a customization component is defined as an Angular component, it can take advantage of built-in Angular mechanisms, like the currency pipe to format values.

To make it possible for developers to use customization components implemented as Angular components, we need to use the dynamic components mechanism provided by the framework. Since the DOM of ag-Grid is not controlled by Angular, we need the possibility to retrieve the DOM rendered by Angular for a customization component and render that DOM in an arbitrary place inside the grid. There are many other architectural pieces required to enable developers to customize the grid through Angular components and we’ll now take a look briefly at the most important ones. Here’s the explanation of how we implemented this mechanism in ag-Grid.

Implementation

We represent each customization component using a generic wrapper component DynamicAgNg2Component. This component keeps a reference to the original customization Angular component implemented by a developer. When ag-Grid needs to instantiate an original component, it creates an instance of the generic DynamicAgNg2Component that’s responsible for using dynamic components mechanism to instantiate an Angular component. Once it obtains the reference to the instantiated dynamic component, it retrieves the DOM created by Angular and assigns it to the _eGui property of the wrapper component. The DynamicAgNg2Component component also implements the getGui method that ag-Grid uses to obtain the DOM from a customization component. Here we simply returned the DOM retrieved from a dynamic Angular component.

Here’s how it all looks in code. The DynamicAgNg2Component component extends the BaseGuiComponent it delegates all work to the init method of the class:

classDynamicAgNg2ComponentextendsBaseGuiComponent{init(params){_super.prototype.init.call(this,params);this._componentRef.changeDetectorRef.detectChanges();};...}

Inside the init method of the BaseGuiComponent is where a dynamic component is initialized and the DOM is retrieved. Once everything is setup, we run change detection manually once and forget about it.

The BaseGuiComponent implements a few required methods for the communication with ag-Grid. Particularly, it implements the getGui method that ag-Grid uses to obtain the DOM that needs to be rendered inside the grid:

classBaseGuiComponent{protectedinit(params:P):void{...}publicgetGui():HTMLElement{returnthis._eGui;}}

As you can see, the implementation of the getGui is very trivial. We simply return the value of the _eGui property. This property holds the DOM created for a dynamic component by Angular. When we dynamically instantiate a component, we obtain its DOM and assign it to the _eGui property. This happens in the init method.

Before we take a look at the implementation of the method, let’s remember that to dynamically instantiate components in Angular we need to get a factory for Angular components. The factory can be obtained using a ComponentFactoryResolver. That’s why we inject it to the main AgGridNg2 when the component is initialized:

@Component({selector:'ag-grid-angular',...})exportclassAgGridNg2implementsAfterViewInit{...constructor(privateviewContainerRef:ViewContainerRef,privateframeworkComponentWrapper:Ng2FrameworkComponentWrapper,private_componentFactoryResolver:ComponentFactoryResolver,...){...this.frameworkComponentWrapper.setViewContainerRef(this.viewContainerRef);this.frameworkComponentWrapper.setComponentFactoryResolver(this._componentFactoryResolver);}}

We also inject ViewContainerRef and Ng2FrameworkComponentWrapper services. The latter is used to wrap an original customization component provided by a developer into the DynamicAgNg2Component. The view container is used to render DOM and make change detection automatic. We run change detection manually only once in the init method of the DynamicAgNg2Component once the component is rendered. By injecting ViewContainerRef into the AgGridNg2 we turn this top level component a container and all dynamic customization components are attached to this container. When Angular checks the top-level AgGridNg2 component, all customization components are checked automatically as part of change detection process.

Let’s now take a closer look at the init method:

classBaseGuiComponent{protectedinit(params:P):void{this._params=params;this._componentRef=this.createComponent();this._agAwareComponent=this._componentRef.instance;this._frameworkComponentInstance=this._componentRef.instance;this._eGui=this._componentRef.location.nativeElement;this._agAwareComponent.agInit(this._params);}...}

Basically, inside the createComponent we delegate the call to the createComponent method of the Ng2FrameworkComponentWrapper. As you remember, this service keeps the references to the ViewContainerRef and componentFactoryResolver that were attached to it during the instantiation of AgGridNg2. In the createComponent method it uses them to resolve a factory for the customization component and instantiate the component:

exportclassNg2FrameworkComponentWrapperextends...{...publiccreateComponent<T>(componentType:{new(...args:any[]):T;}):ComponentRef<T>{letfactory=this.componentFactoryResolver.resolveComponentFactory(componentType);returnthis.viewContainerRef.createComponent(factory);}}

Then using the component reference we get the DOM and attach it to the eGui private property:

this._componentRef=this.createComponent();this._agAwareComponent=this._componentRef.instance;this._frameworkComponentInstance=this._componentRef.instance;this._eGui=this._componentRef.location.nativeElement;

And that’s it. If you’re interested to learn how we implemented the component resolution process, continue reading.

Component resolution process

ag-Grid is a very complex piece of software. To simplify things internally we’ve designed and implemented our own Dependency Injection (IoC) system that’s modeled after Spring’s IoC container and beans. The component resolution process requires a bunch of services that are registered in this DI system. The most important ones are ComponentResolver and ComponentProvider. Also, we need the Ng2FrameworkComponentWrapper service that is specific for ag-Grid used as Angular wrapper. It’s registered in the DI system using frameworkComponentWrapper token.

Resolution is performed through the ComponentResolver service. When the resolver is instantiated, the frameworkComponentWrapper and componentProvider services are attached to the resolver through the DI system and are available on the class instance:

@Bean('componentResolver')exportclassComponentResolver{@Autowired("gridOptions")privategridOptions:GridOptions;@Autowired("componentProvider")privatecomponentProvider:ComponentProvider;@Optional("frameworkComponentWrapper")privateframeworkComponentWrapper:FrameworkComponentWrapper;...}

When the grid needs to instantiate a particular type of a component, e.g. a cell renderer, it calls ComponentResolver.createAgGridComponent method. The method uses a descriptor of a column to obtain the name of a component that needs to be created. For the cell renderer component the property that contains the name of a component is cellRenderer:

letcolumnDefs=[{headerName:'Price',field:'price',editable:true,cellRenderer:'numberFormatterComponent'},...]

Once the name is obtained, it is used to retrieve the component class and metadata from the componentProvider:

exportclassComponentResolver{privateresolveByName(propertyName,...){constcomponentName=componentNameOpt!=null?componentNameOpt:propertyName;constregisteredComponent=this.componentProvider.retrieve(componentName);...}}

The retrieve method returns the following descriptor of a component:

{component:NumberFormatterComponentdynamicParams:nullsource:1type:Component_Type.Framework}

The type of a component denotes that it’s a framework specific component. All framework components are wrapped into the DynamicAgNg2Component as explained the first section of the article. Once the component is wrapped, it contains the getGui method common to all customization components and ag-Grid can work with it as if it’s a plain JavaScript component.

Angular Master Class coming to Málaga

$
0
0

Angular Master-Class Training

You may have noticed that we recently asked the Angular community where to host our next public Angular Master Class.

Today we are happy to announce that the community has decided: We will come to MÁLAGA!

It’s been a while but now we are excited to finally hold another public Angular Master Class and bring it to the beautiful city of Málaga in Spain this October! We can’t wait for it!

We’ve also found a really great venue in Málaga that will set the right balance between work and fun: The Innovation Coworking Campus

AMC Malaga Event Location

We are sure this place will get everyone into the right state of mind to learn the nuances and power of Angular!

About the Training

The event takes place on the 28th to 30th October 2019 in the heart of the vibrant city Málaga.

We will have three (3) days of fun and learning the ins and outs of Angular including things like:

  • Components and Modules
  • Fetching data using Http
  • Advanced Dependency Injection
  • Advanced Forms
  • Observables deep-dive
  • Architectural patterns
  • ContentChildren and ViewChildren
  • Statemanagement with ngrx
  • …and many more!

Tickets on sale now

You can find more information on our official event page, or grab your public training ticket right here!

See you in Málaga!

On-Premise, Private Training?

Private, on-site Angular Training by thoughtram

If you prefer private, on-site training for your development teams, please contact us for rates, details, and scheduling: hello@thoughtram.io