Category: Angular

  • How to fix an Angular component that won’t stick

    How to fix an Angular component that won’t stick

    If you’re working in a component-driven framework like Angular, you might notice one day that your components start ignoring your router’s scroll events and are no longer sticky. All of a sudden, you find the only way to keep your components from scrolling off the page is by assigning them a fixed position with CSS. The only reason this works is because the closest positioned ancestor your components can find is the last possible resort, Document, which represents the whole DOM.

    What do you mean by ‘sticky’?

    Just to be clear, I’m referring to the CSS property ‘position’ that has the following possible values:

    1. static – no special positioning
    2. relative – normal positioning
    3. fixed – always in the same place, regardless of scroll
    4. absolute – always in the same place relative to its closest ancestor that has positioning
    5. sticky – always in the same place after it reaches a top, right, bottom, or left scroll position

    Using sticky is like fixed + relative.

    Fixed is cool if you only care about positioning in respect to the viewport. But using it means your components no longer take space in the DOM, and therefore no longer have any relation to the positioning of everything else in the DOM. This runs the risks for things like overlapping, cropped and/or empty content.

    What kinds of issues are we talking about?

    1. Applying sticky has no effect on component. It scrolls out of viewport with everything else.
    2. Applying sticky has partial effect on component. It stays in the right spot until you scroll past the entire length of the screen.

    Steps to troubleshoot

    Make sure you’ve specified at least a top, left, right or bottom position on the component you’re trying to make sticky.

    You can used fixed or relative units, but you must specify at least a top, left, right or bottom position or else it won’t know where to stick.

    Make sure your component’s container has a defined height or width.

    Your component needs to know the dimensions of its container in order to calculate where the sticking point is in relation to itself. It will continue checking its ancestors until it finds one with positioning.

    Make sure your component’s container does NOT have overflow set to hidden.

    Once you’ve scrolled your component out of the viewport, it is considered overflow. Therefore, hiding the overflow on a sticky element that needs overflow to stick won’t work.

    But what if the height is dynamic?

    Let’s say you want the footer to always start at the bottom of the page, even if there’s not enough content to push it down. If you set the component’s container height to 100% and it doesn’t stretch to the end of the page, you might try 100vh. While that works and your component might be sticky at first, you may notice it stops sticking after you’ve scrolled past the full length of the page.

    To solve this problem, set the following styles on your component’s container:

    .container {
       ...
       min-height: 100vh;
       height: 100%;
     }
    
  • Migrate your build scripts for sexy Angular SSR

    Migrate your build scripts for sexy Angular SSR

    If you’re migrating build scripts from an older project into one that uses Angular 17+, you might run into the error:

    Project target does not exist.
    

    Why it happens

    Angular 17 no longer has a separate build command for SSR! Also, it will complain if you try to use the –production flag.

    The fix

    In package.json, remove the script with ‘build:ssr’ as the key. It will be located under ‘scripts’:

    "scripts": {
      "build:ssr": ".." -- you need to delete this key-value pair
    }
    

    Prerendering

    As a bonus, Angular will also run the prerender command automatically when it builds. (It’s a setting in angular.json if you want to change it.)

  • Why Angular components, pipes, and directives should stand alone and what that means for modules

    Why Angular components, pipes, and directives should stand alone and what that means for modules

    Don’t get me wrong – I really love Angular modules. They are like that office manager who keeps track of everything in their head and runs everything like a nearly infallible machine.

    But if you are developing a UI component library, it gets very tedious to have to create a module just so something can use your one component. Modules are meant for groupings, and they are great at that. But for generic components, they can be overkill.

    What difference does a module make?

    Before standalone, the only way to use a component, directive, or pipe was to declare them in a module that had to be imported by whoever wanted to use them. If you declared multiple items in the same module, it became hard to track what was being imported for what and what was no longer being used. This ran the risk of importing things unnecessarily, causing bloated bundle sizes and subsequently slower app times.

    As of Angular 14, the brilliant Angular team added standalone functionality. In other words, components, directives, and pipes can import what they need directly without modules just like any other import.

    You can import components, directives or pipes directly to use them!

    Lazy load with less files

    Before standalone, the only way to lazy load was to create a module with routing to serve as a locator to get to the correct component without any knowledge of what that component is. Now we can point to the component with imports instead of routes.

    Should I switch everything to standalone?

    I can’t think of a good reason to declare components, directives, and pipes with modules ever again. Please let me know if you can think of reasons why you should.

    What good are modules then?

    Modules are still super useful, just less needed…like a lot less needed. Actually, if you use them just for grouping and are no longer concerned with the dependencies of what you’re declaring, how to best use modules will become a lot clearer.

    For example, if you’re importing the same group of components, directives, and/or pipes across multiple places or want to alter what’s being imported in one spot, you should bundle them in a module. Don’t use the module to declare anything. Only use it for importing/exporting what you need.

    Another example would be if you are using things that rely on each other, like a component that uses a directive to interact with its children. But even then, just use the module to import/export.

    Which Angular decorators can be standalone?

    • Components
    • Directives
    • Pipes

    How do you become standalone?

    By Angular CLI

    ng g c componentName --standalone
    

    By component metadata

    @Component({
      standalone: true,
      selector: 'your-selector',
      imports: [
        CommonModule,
        ExampleModule
      ],
      ...
    })
    

    If you’re using an existing decorator, you need to delete it from the module that’s declaring it. You can be declared by a module or have standalone turned on but not both.

  • The right way to componentize SVGs for your Angular app

    The right way to componentize SVGs for your Angular app

    Last year, I wrote an article about how to create an SVG library in Angular by making components that link to SVG files instead of HTML templates. Shortly afterwards, my boss tasked me with finding a solution to the excessive DOM size problem, which we discovered was being caused by all of the elements that make up our SVGs.

    How to create references instead of actual clones to your SVGs

    After doing extensive research and trying different methods, I came across the ‘symbol’ and ‘use’ HTML elements. This article gives great in-depth explanations into why this is the correct approach.

    If you have ever worked in Flash…I mean Animate, you will remember how awesome symbols were. They enabled you to create one symbol and then reference that symbol so any changes you did to the original were cascaded to all of its references. By using ‘symbol’ and ‘use’, you are essentially doing the same thing.

    To make this solution even more enticing, the ‘use’ element acts as a container and stores its children in the shadow DOM. In other words, your SVG elements no longer count towards your DOM size.

    The drawbacks of the shadow DOM and how to overcome them

    There are some drawbacks though with using a closed shadow DOM. For example, you can’t access or manipulate the SVG’s viewBox through JavaScript. Therefore, you won’t be able to do things like pull out the SVG’s exact dimensions without inspecting the element through a browser or updating the paths of the SVG without an image editor.

    This can be problematic because if you don’t set the SVG’s width and height, it will automatically have a width of 300 pixels and a height of 200 pixels. If your SVG is considerably smaller, you will see a lot of blank space around the SVG. Because we’re going to create one component for all of your SVGs, you can’t set one universal viewBox to handle this problem.

    The viewBox isn’t the only thing that’s inaccessible. All of the SVG’s styles are as well. This is actually a pretty good benefit because a change to the original would be applied to all of its references.

    Don’t fret; we can easily overcome these hurdles by making our SVG generic and adaptable. We can use properties like ‘inherit’ on the SVG’s width and height and ‘currentColor’ for its fill and outline so they can be set through a parent or ancestor.

    How to create the SVG component

    Step 1: Create a graphic and export it as an SVG

    Note: In order to individually style your SVG and have it scale gracefully to any size, you must use actual paths instead of embedded images for your SVGs.

    For example, I made my logo with vector art using Illustrator. This is what it looks like when I export just the cloud as an SVG:

    <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 229 149.04">
      <path d="M189.37,142a12.35,12.35,0,0,1-3.2-3c-1.26-1.65-2.31-3.5-3.71-5a12.12,12.12,0,0,0,2.48,4.77A12.55,12.55,0,0,0,189.37,142Z" transform="translate(0 0)" style="fill:#ccc"/>
      <path d="M192.89,142.94a12.66,12.66,0,0,1-3.52-1A6.19,6.19,0,0,0,192.89,142.94Z" transform="translate(0 0)" style="fill:#ccc"/>
      <path d="M182.46,133.91a11.83,11.83,0,0,1-.4-3.65,5,5,0,0,0-1.2,2.21,1.51,1.51,0,0,1,.26.18A10.64,10.64,0,0,1,182.46,133.91Z" transform="translate(0 0)" style="fill:#ccc"/>
      <path d="M229,76.35a28.33,28.33,0,0,0-19-26.61,14.29,14.29,0,0,0,1.32-6,14.8,14.8,0,0,0-14.93-14.67h-.09a15.07,15.07,0,0,0-10.47,4.2,27.92,27.92,0,0,0,.45-5C186.28,12.67,173.36,0,157.43,0a28.94,28.94,0,0,0-25.81,15.64,24.52,24.52,0,0,0-2.7-.2A29.22,29.22,0,0,0,113.33,11C97.4,11,84.48,23.62,84.48,39.25a28.41,28.41,0,0,0,.15,2.92c-2.79-.57-5.76-1.72-8.51-2.21-4.42-.54-8.85,2.71-12.19,5.47a36.36,36.36,0,0,0-3.47,3.66A31.66,31.66,0,0,0,52.9,67.61c-.56,13,10,21.14,18.8,29.27,1.1,1.63,0,4.88,1.1,5.42,3.32,3.26,6.64-2.71,6.08-6-1.1-4.88-6.08-6-8.84-9.22-3-3.25-2.91-7.58-1-11.31a14.81,14.81,0,0,1,2.49-3.44c3.45-4.22,9-3.68,13.41-2.59,7.19,2.17,14.93,6,16.6,13.59-.06,1.62-.15,3.29-.28,5-1.06,13.49-4.32,26.82-15.67,35.58-8.37,6.46-19.16,9.14-29.75,10.3s-21.32.93-31.87,2.4c14.49-4.51,29.91-5.73,44.28-10.58,7.48-2.53,14.82-6.18,19.89-12.12a35.54,35.54,0,0,0,7.68-17.8c1.05-6.79.27-14.5-4.81-19.23-3.5-3.26-9.84-4.22-12.87-.53s-1.07,9,1.5,12.82A19.63,19.63,0,0,1,78,83.06a6.67,6.67,0,0,1,2.37-5.66c2.14-1.58,5.3-1.22,7.48.29a13.06,13.06,0,0,1,4.46,6.37,28.19,28.19,0,0,1-3,25.38C82.12,120,68.58,124.6,55.72,126.23,46.5,127.4,37.17,127.4,28,128.57S-12.06,144.48,5.1,148c16,3.23,88.05-1.41,106.45-9.34s34.35-20.07,54.15-24.38c5-1.09,9.4,0,13.82,2.71,7.19,4.34,6.08,11.92,6.08,18.43,0,2.17,2.21,4.88,4.43,4.88,7.74,0,13.82-7.59,14.37-14.64-.56,7.19-5.48,20.7-15.52,16.76-2.32-.91-4.12-2.73-5.85-4.5a6.9,6.9,0,0,0,2.39,3.76,13.57,13.57,0,0,1-2.71-2.53,9,9,0,0,1-2-4.36,6,6,0,0,1,.14-2.27,1.53,1.53,0,0,0-.51-.21,1.2,1.2,0,0,0-1.17.87c-1.87,5.72,7.7,10.77,12.16,11.31,9.91,1.22,17.12-7.21,19.47-15.79a43.86,43.86,0,0,0,1.13-5.79,23.09,23.09,0,0,0,0-7.42c-.18-1.22-.43-2.44-.72-3.66a21.87,21.87,0,0,0-1.13-4.29c-.48-1.38-1-2.76-1.39-4.12A28.37,28.37,0,0,0,229,76.35Zm-39.19,67.34a21.89,21.89,0,0,0,5.3.41,13.85,13.85,0,0,1-5.32-.41Z" transform="translate(0 0)" style="fill:#ccc"/>
     </svg>
    

    Step 2: Prepare your SVG

    The next step is to add ‘symbol’ tags to the elements inside of your SVG and to move the viewBox to the newly created ‘symbol’ element. You must also add an id attribute to the symbol so that you can call it externally.

    If you want to change the colors differently for each reference, you have to set the ‘fill’ and ‘outline’ attributes to ‘currentColor’ or ‘inherit’. Use ‘currentColor’ to set the color through the ‘color’ property. Use ‘inherit’ to set the color through the ‘path’ property.

    I also learned a trick from using Font Awesome icons. You can automatically make the SVG be two-colored by setting the opacity of the secondary color.

    Now, your SVG will look something like this:

    <svg xmlns="http://www.w3.org/2000/svg">
      <symbol id="ces-logo-cloud"  viewBox="0 0 228.98 148.98">
        <path opacity="0.4" d="M189.37,142a12.22,12.22,0,0,1-3.2-3c-1.26-1.65-2.31-3.5-3.71-5a12.12,12.12,0,0,0,2.48,4.77A12.6,12.6,0,0,0,189.37,142Z"/><path opacity="0.4" d="M192.89,142.94a12.74,12.74,0,0,1-3.52-1A6.2,6.2,0,0,0,192.89,142.94Z"/><path opacity="0.4" d="M182.46,133.91a11.83,11.83,0,0,1-.4-3.65,5,5,0,0,0-1.2,2.21,2.4,2.4,0,0,1,.26.18A10.68,10.68,0,0,1,182.46,133.91Z"/><path fill="currentColor" d="M229,76.35a28.34,28.34,0,0,0-19-26.61,14.29,14.29,0,0,0,1.32-6A14.8,14.8,0,0,0,196.3,29.07a15.06,15.06,0,0,0-10.47,4.2,28.12,28.12,0,0,0,.45-5C186.28,12.67,173.36,0,157.43,0a28.92,28.92,0,0,0-25.81,15.64,24.52,24.52,0,0,0-2.7-.2A29.18,29.18,0,0,0,113.33,11C97.4,11,84.48,23.62,84.48,39.25a28.26,28.26,0,0,0,.15,2.92c-2.79-.57-5.76-1.72-8.51-2.21-4.42-.54-8.85,2.71-12.19,5.47a37.46,37.46,0,0,0-3.47,3.66A31.68,31.68,0,0,0,52.9,67.61c-.56,13,10,21.14,18.8,29.27,1.1,1.63,0,4.88,1.1,5.42,3.32,3.26,6.64-2.71,6.08-6-1.1-4.88-6.08-6-8.84-9.22-3-3.25-2.91-7.58-1-11.31a14.86,14.86,0,0,1,2.49-3.44c3.45-4.22,9-3.68,13.41-2.59,7.19,2.17,14.93,6,16.6,13.59-.06,1.62-.15,3.29-.28,5-1.06,13.49-4.32,26.82-15.67,35.58-8.37,6.46-19.16,9.14-29.75,10.3s-21.32.93-31.87,2.4c14.49-4.51,29.91-5.73,44.28-10.58,7.48-2.53,14.82-6.18,19.89-12.12a35.52,35.52,0,0,0,7.68-17.8c1.05-6.79.27-14.5-4.81-19.23-3.5-3.26-9.84-4.22-12.87-.53s-1.07,9,1.5,12.82A19.62,19.62,0,0,1,78,83.06a6.66,6.66,0,0,1,2.37-5.66c2.14-1.58,5.3-1.22,7.48.29a13.1,13.1,0,0,1,4.46,6.37,28.18,28.18,0,0,1-3,25.38C82.12,120,68.58,124.6,55.72,126.23,46.5,127.4,37.17,127.4,28,128.57S-12.06,144.48,5.1,148c16,3.23,88.05-1.41,106.45-9.34s34.35-20.07,54.15-24.38c5-1.09,9.4,0,13.82,2.71,7.19,4.34,6.08,11.92,6.08,18.43,0,2.17,2.21,4.88,4.43,4.88,7.74,0,13.82-7.59,14.37-14.64-.56,7.19-5.48,20.7-15.52,16.76-2.32-.91-4.12-2.73-5.85-4.5a6.88,6.88,0,0,0,2.39,3.76,13.55,13.55,0,0,1-2.71-2.53,8.92,8.92,0,0,1-2-4.36,6,6,0,0,1,.14-2.27,1.42,1.42,0,0,0-.51-.21,1.19,1.19,0,0,0-1.17.87c-1.87,5.72,7.7,10.77,12.16,11.31,9.91,1.22,17.12-7.21,19.47-15.79a44.22,44.22,0,0,0,1.13-5.79,23.09,23.09,0,0,0,0-7.42c-.18-1.22-.43-2.44-.72-3.66a21.43,21.43,0,0,0-1.13-4.29c-.48-1.38-1-2.76-1.39-4.12A28.37,28.37,0,0,0,229,76.35Zm-39.19,67.34a21.82,21.82,0,0,0,5.3.41A13.73,13.73,0,0,1,189.79,143.69Z"/>
      </symbol>
    </svg>
    

    Step 3: Create a component that will fetch your SVGs

    svg.component.ts

    import { Component, Input, OnInit } from '@angular/core';
    import { SvgSize } from './svg';
    
    @Component({
      selector: 'ces-svg',
      templateUrl: './svg.component.html',
      styleUrls: ['./svg.component.scss']
    })
    export class SvgComponent implements OnInit {
    
    link: string = '';
    
    @Input() size: SvgSize | undefined;
    @Input() type: string = '';
    @Input() dropShadow: boolean = true;
    
    constructor() {
    }
    
    ngOnInit(): void {
      this.link = `/assets/images/svgs/${this.type}.svg#${this.type}`;
    }
    

    It’s crucial to remember you can’t just link a file to the SVG. You must add a hash tag and the name of your id to the end of the file name after the file extension. You can save on http requests if you house multiple SVGs in one file, aka sprites. But you must use unique ids for each SVG in the file.

    Also, if you want to add classes to apply styles that fit exactly to the shape of the SVG like drop shadows or borders, you have to add them directly to the SVG element or else they will be applied to the component host. However, you can define those classes in the component or as a global class.

    svg.component.html

    <svg [ngClass]="{ 'drop-shadow' : dropShadow }" [class]="size ? size : ''" xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink">
      <use [attr.xlink:href]="link" [attr.href]="link" />
    </svg>
    

    I’m specifying the two href attributes because ‘xlink:href’ is being deprecated and is just there for backwards compatibility.

    svg.component.scss

    @import 'variables';
    
    svg {
      width: inherit;
      height: inherit;
    }
    
    .xs {
      width: $icon-xs-sm;
      height: $icon-xs-sm;
    }
    
    .sm {
      width: $icon-sm-sm;
      height: $icon-sm-sm;
    }
    
    .md {
      width: $icon-md-sm;
      height: $icon-md-sm;
    }
    
    .lg {
      width: $icon-lg-sm;
      height: $icon-lg-sm;
    }
    
    @media (min-width: $breakpoint-md) {
      .xs {
        width: $icon-xs-md;
        height: $icon-xs-md;
      } 
    
      .sm {
        width: $icon-sm-md;
        height: $icon-sm-md;
      }
    
      .md {
        width: $icon-md-md;
        height: $icon-md-md;
      }
    
      .lg {
        width: $icon-lg-md;
        height: $icon-lg-md;
      }
    }
    

    Don’t forget to set the width and height to ‘inherit’ using the ‘SVG’ selector or else you won’t be able to set them outside of the component.

    In order to get around the problem of not being able to view or set the viewBox per reference, I use art boards with square dimensions when exporting SVGs. That way, I don’t have to worry about getting the exact width/height ratio correct and can easily avoid distorted graphics.

    In order to enforce standardized sizes, I also created different classes for each size. I defined them using variables so different apps that use this component can have their own standardized sizes.

    Example Usage

    Alas, here is an example of how to use the SVG component we just created in a button:

    <button class="inherit" [ngClass]="{ 'active' :  active }" (click)="toggleEdit()">
      <ces-svg size="xs" name="pencil"></ces-svg>
    </button>
    

    The SVG’s styles that we set earlier will automatically inherit those of the button. But they can also be set by adding a class to the SVG component.

    Source Files

    Here is a link to the source files. Please send me any thoughts or ideas for improvement. Thanks!

  • Set your parents free with Angular’s dependency injection

    Set your parents free with Angular’s dependency injection

    If you’re using a modular, component-driven architecture, one of the greatest challenges you will face is how to enable communication between parent and child components/directives. On the one hand, you want each component/directive to be independently focused on their purpose. On the other hand, you need them to work together as though they were built as a cohesive unit.

    Luckily for us, Angular provides everything you need for any type of situation!

    If your parent view contains the children, a common approach is to use the @Input() and @Output() decorators. To communicate with the parent, you can emit custom events through your children. To communicate with the children, you can pass properties that are bound to your parent.

    You can also use @ViewChild/@ViewChildren and @ContentChild/@ContentChildren to break down the parent/child walls. (I will discuss these powerful decorators in future articles.)

    If your parent view doesn’t contain the children directly or perhaps there is no relationship at all, you can use observables in a service to communicate data. That way, it doesn’t matter what the actual relationship is because anyone who injects the service can update and subscribe to the observables.

    Why use dependency injection to find a parent

    I created a tasking system that displays each task in a table row component. I also created button components to take actions on the table row. However, I didn’t want to include the button components in the table row component so they could be used regardless if they were in a table or not.

    The problem I faced was how to assign the button components a specific ID without having to pass the ID as an input over and over. For example, here is the HTML for the table row component that iterates over an array of tasks.

        <ces-table-row *ngFor="let task of tasks; let index = index;" [id]="task.id" [index]="index">
        <ces-table-column class="justify-center">
            <ces-view-pin [active]="task.pinned"></ces-view-pin>
            <ces-view-delete></ces-view-delete>
        </ces-table-column>
        <ces-table-column class="justify-center">{{ task.id }}</ces-table-column>
        <ces-table-column class="justify-center">
            <ces-task-menu-statuses [default]="task.status"></ces-task-menu-statuses>
        </ces-table-column>
        <ces-table-column>{{ task.dateUpdated | date: 'short' }}</ces-table-column>
        <ces-table-column>{{ task.title }}</ces-table-column>
        <ces-table-column>{{ task.notes }}</ces-table-column>
    </ces-table-row>
    

    The button components I mentioned use the selectors ‘ces-view-pin’ and ‘ces-view-delete’. I wanted these components to only be responsible for the state of the button interaction and to send a message to a service when they are pressed. That way, I could avoid having to repeat redundant code each time they are used and they would behave the same way no matter who was using them.

    Since the table row component is getting the task ID, I needed a way to navigate up the hierarchy to access it. Thankfully, Angular provides an easy way to search the hierarchy.

    How to search for a parent interface

    Step 1: Create an abstract class

    I started by creating an abstract class that contained the properties I wanted the button components to be able to access.

    export abstract class View {
      abstract id: number;
      abstract index: number;
    }
    

    Step 2: Implement the abstract class and add the provider

    The next step is to implement the abstract class and to provide a reference for anyone who tries to inject it.

    import { Component, forwardRef, Input } from '@angular/core';
    import { View } from 'projects/view/src/public-api';
    
    @Component({
      selector: 'ces-table-row',
      templateUrl: './table-row.component.html',
      styleUrls: ['./table-row.component.scss'],
      providers: [{
        provide: View, useExisting: forwardRef(() => TableRowComponent)
      }]
    })
    export class TableRowComponent implements View {
      @Input() id!: number;
      @Input() index: number = -1;
    
      constructor() { }
    }
    

    Step 3: Inject the interface into the child component/directive

    The last step is to inject the View Class using the @Optional() decorator to any child who needs to access the parent’s properties/methods that was defined in the abstract class.

    export class ViewDeleteComponent {
    
      constructor(
        protected _viewService: ViewService,
        @Optional() public view: View
      ) { }
    
      delete() {
        const message: ApiChange = {
          id: this.view.id,
          index: this.view.index,
          action: 'delete',
        }
    
        this._viewService.setChange(message);
      }
    }
    

    View this github gist if you want to see the code for all three components.

  • How to solve ENOTEMPTY: Directory not empty

    How to solve ENOTEMPTY: Directory not empty

    I recently redesigned my blog’s website and set up the upload process so that I wasn’t dragging files to be uploaded directly from Visual Studio Code. As a result, I started getting this error each time I tried to build the site:

    An unhandled exception occurred: ENOTEMPTY: directory not empty, rmdir...
    

    Since I’m using Angular, I noticed that the ‘dist’ folder (the one Angular publishes your build to) was deleting my assets but not the actual folder like it used to. I found a couple of Stack Overflow threads that offered solutions to bypass this error. Deleting the nodes_modules folder and reinstalling express body parser worked once but the error kept coming back on subsequent builds.

    It turns out that the problem wasn’t a permissions one which I initially suspected. It was acutally quite simple…I just had a finder window open with that directory! Anyway, just thought I’d share since it was such a stupid mistake.

    To resolve the error, all you have to do is close any dialogs that have the ‘dist’ folder open. Whoops!

  • Go faster than navigation with Angular resolvers

    Go faster than navigation with Angular resolvers

    I remember the first time I read about Angular resolvers, they sounded so cool. Let the router prefetch data? Yes, please! But when I tried to follow along the tutorial on Angular’s website, I got a little lost.

    While the writer did a really amazing job of breaking everything down, I kept running into null injector errors because it’s a little different when you go from lazy-loaded library to library rather than straight from a module to your app. And you really need to understand the difference between the router, the activated route, and the router state to truly appreciate how cool Angular resolvers can be.

    What is an Angular resolver anyway?

    Think of an Angular pipe but one you use before the page even loads! That means you can transform or fetch data without having to subscribe to the Activated Route! In other words, no more waiting!

    Resolvers can greatly enhance your app or website experience because they start the moment the user decides to navigate somewhere. In fact, the page won’t even load until your resolver is finished. So, you don’t have to scramble with race conditions and can be prepared for when they arrive.

    Keep in mind we’re talking about milliseconds, so don’t go too crazy. Here are some use cases that I started working on:

    1. Generate and update meta tags
    2. Fetch blog articles
    3. Grab dynamic parameters to conduct search queries
    4. Create navigation menus based on your routing module

    How do they work?

    Imagine having a totally separate service that exists outside of your module act as though it was actually inside of your module. It uses the ActivatedRouteSnapshot and RouterStateSnapshot as arguments to its resolve method, so that it can use the same exact routing data you get with ActivatedRoute and RouterState to send you data.

    Therefore, instead of subscribing to routing events, it has data prepared already for you! All you have to do is add extra parameters to your routing module, like you do with path, component, and data. So at the same level, just add a resolve parameter that states what key to use and which resolver should provide the data. Then, get the activated route’s snapshot to retrieve the data.

    Can you give me an end-to-end example?

    Sure! A while back, I wrote an article about creating a self-updating navigation menu. While I really liked the aspect of updating the route and meta information in one place, I didn’t like having to create multiple menus even if it was super simple and quick to do so.

    So, now to create a navigation menu, I can add the following to my routing module:

        resolve: {
          displayTitle: MetaTagsResolverRouteDataService,
          menu: ResolveMenuService
         },
         
    

    Note: ‘displayTitle’ and ‘menu’ are arbitrary values that are only declared here. They will be used as keys when it’s time to find your route’s data.

    You have the option of using the resolver at the module, route, or component level. Remember, if you provide it to a parent route, all of its children can readily access it. However, you might want to keep in mind that it will fire every time a user navigates to that route.

    demos-routing.module.ts

    Next, I added data to the routing module to specify what values to use.

      {
        path: 'select-menus',
        component: DemoSelectMenusComponent,
        data: {
          title: 'Select Menus',
          description: "Cloud engineering demo of customizable select menus",
          menu: {
            icon: 'dropdown',
            label: 'Select Menus',
            show: true,
            activeSelfOnly: true,
          },
        },
       },
    

    resolve-menu.service.ts

    My resolver is super simple. It just parses out the data that’s needed to build the navigation menu and returns it.

    One thing to note is that there can be parent/child hierarchies depending on how you set up your routes. Almost always, my menus will be at the parent level because I want them to be on every page that belongs to their library. You can create a library-specific template with consistent layouts by using Angular’s router outlet.

    import { Injectable } from '@angular/core';
    import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from   '@angular/router';
    import { ResolveMenu } from './menus';
    import { Observable } from 'rxjs';
    
    
    @Injectable({
      providedIn: 'root'
    })
    
    export class ResolveMenuService implements Resolve<ResolveMenu[]> {  
    
      constructor() { 
      }
    
      resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
      ): ResolveMenu[] | Observable<ResolveMenu[]> {
    
      let menu: ResolveMenu[] = [];
      route.routeConfig.children.map(child => {
         if (child.data.menu)
            menu.push({
            path: child.path,
            show: child.data.menu.show,
            activeSelfOnly: child.data.menu.activeSelfOnly,
            title: child.data.title,
            label: child.data.menu.label,
            icon: child.data.menu.icon
          })
        })
      return menu;
      }
    }
    

    menus.demo.component.ts

    Finally, to see the resolved data, I can call it like so:

    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute } from '@angular/router';
    import { ButtonRoute } from 'projects/buttons/src/public-api';
    
    @Component({
      selector: 'ces-demos-menu',
      templateUrl: './demos-menu.component.html',
      styleUrls: ['./demos-menu.component.scss']
    })
    
    export class DemosMenuComponent implements OnInit {
    
      buttons: ButtonRoute[] = [];
    
      constructor(
        private route: ActivatedRoute,
      ) {}
    
      ngOnInit(): void {
        this.buttons = this.route.snapshot.data.menu;
      }
    }
    

    The resolved data is stored in the ActivatedRouteSnapshot and RouterStateSnapshot. Just look for whatever key you added when specifying the route, and it will be available in JSON format.

    demos-menu.component.html

    To render the menu, I just pass this.buttons value to the button route component I created to handle routes:

    <nav>
      <ces-menu-toggle class="nav-buttons align-left" menuTitle="navigation" toggleIcon="ellipsis-vertical">
      <ces-button-route *ngFor="let button of buttons" [link]="button.path" [title]="button.title" [label]="button.label" [icon]="button.icon" [activeSelfOnly]="button.activeSelfOnly" class="nav-button">
      </ces-button-route>
    </ces-menu-toggle>
    </nav>
    

    And voila, here is the navigation menu in action. I would normally hide it since it looks silly to have a navigation menu for one item, but will keep it up for now in case you want to take a peek. This way, in the future, anytime I add another demo to this section, the menu will automatically get created.

    Here are the Github Gists if you want to see all of the files together.

    Things to watch out for

    Circular references

    You cannot build a resolver inside of the same library/module that will use it. Likewise, you cannot use a service that’s inside of the library/module nor put your resolver in the same library that the target library uses. This will create a circular reference and Angular is smart enough to let you know.

    For example, I created a data library for my blog service to fetch articles from. I was not able to put the resolver in either of those libraries nor use the blog service to fetch the articles. So, I put my resolver in a routing library that uses the data service to fetch the articles.

    It’s best practice anyway to keep everything in one direction so as not to cause unwanted dependencies.

    No provider errors

    Remember, the resolver is like a floating route. So, if you want to use the resolver other than when the user is navigating to a certain route, pass in the ActivatedRoute and RouterState, not the ActivatedRouteSnapshot and RouterStateSnapshot.

    Hopefully, you enjoyed this article. Thanks for reading it!

  • Allow users to edit the content of any element with this Angular directive

    Allow users to edit the content of any element with this Angular directive

    As a side project, I am working on a note taker/learning aid app built with Angular. One of my main goals is to make it as user friendly as possible, so I decided to make an Angular directive that does the following:

    1. Lets user click on text to edit the contents
    2. Can restrict edits by requiring user to hit the edit icon first, essentially entering an edit mode
    3. Checks whether the text was altered when the input loses focus or when the user hits ‘Enter’
    4. Saves the text if it was altered

    To begin, the directive has the following properties:

      // Use this variable to store the original value
      value: string;
      
      // Use this input if you want to restrict editing to only be allowed in edit mode 
      @Input() allowEdit: boolean = true;
      
      // Use this input if you are saving the change and need to reference the identifier of the change
      @Input() id: number;
      
      // Use this input to indicate what field is being changed
      @Input() update: string;
      
      // Use this output to notify the parent that a change has been made
      @Output() onChange: EventEmitter<EditUpdate> = new EventEmitter();
      
    

    For the constructor, you will need:

      // Automatically get a reference to the element you're changing with ElementRef
      private el: ElementRef
      
      // Safely update the element without accessing the DOM directly by using Renderer2
      private renderer: Renderer2
      
    

    Detect events with HostListener

    The beauty of using HostListener is that you don’t have to keep track of who is sending the event because as the name implies, it’s the host of whatever you added the directive to. (To me, this is the true power of directives!)

    That means no more redundant code of individually assigning event listeners and then hooking those up to the functions that contain callbacks of what you want to happen when those events are made! No more having to build hierarchical relationships so that you can modify the element in relation to the elements around it!

    I have three HostListeners for this directive because I want to detect when the user clicks on the element, when the user clicks off of the element, and when the user hits ‘Enter’ on the keyboard.

    A HostListener to detect click events

    If you want to toggle editing capabilities, wrap an if statement around the function that’s handling the click event. Because the parent determines whether or not the element should be editable, you don’t have to worry about toggling this variable. (I’ll cover how to reset the variable further down this article.)

    @HostListener("click") onClick() {
      if (this.allowEdit) {
        this.makeEditable();
      }
    }
    

    Some reasons to make editing not automatic by default

    1. You want only certain users with the proper permissions to be allowed to edit the text.
    2. You want the text to be selectable when it’s not being editing.
    3. You want to limit the possibility of accidental edits.

    HostListeners to detect when the user is done editing

    When first making this directive, I spent a lot of time creating a save/undo button interaction that hid/showed other functionality in the different modes but then later scrapped that approach because it required the user to click unnecessarily. After all, nothing is more frustrating than losing all of your work because you forgot to hit save.

    I created one for when the user clicks elsewhere and also another to listen for the ‘Enter’ key.

     @HostListener('blur') blurred() {
      this.checkValues();
    }
    
    @HostListener('keydown', [('$event')]) onKeydown(event) {
      if (event.keyCode === 13) {
         event.preventDefault();
         this.checkValues();
    
      }
    }
    

    Enter Edit Mode

    You can use ElementRef to identify the element rather than querying the DOM and Renderer2 to manipulate it. This function simply adds the editing class to the element, thereby providing visual feedback to the user that it’s now editable.

    makeEditable() {
      this.renderer.addClass(this.el.nativeElement, "editing");
      this.renderer.setAttribute(this.el.nativeElement, "contentEditable", "true");
      this.el.nativeElement.focus();
      this.setValue(this.el.nativeElement.innerText);
    }
    

    Check to see if an edit was made

    Next, set the ‘contentEditable’ attribute to ‘true’. Then, give it focus and save the value of the inner text.

    After the user finishes editing, we’re going to compare the value of the inner text with the previous one to see if there are any changes. That way, we’ll send the data to the parent only if there is a difference and thus reduce the number of unnecessary trips to the database.

    checkValues(): Observable<EditUpdate> {
      const newValue = this.el.nativeElement.innerText;
    
      if (this.value !== newValue) {
        const edit: EditUpdate = {
        id: this.id,
        update: this.update,
        value: newValue
        }
        this.onChange.emit(edit);
      }
      
      this.reset();
      return this.onChange;
    }
    

    Exit Edit Mode

    Don’t forget to reset edit mode by removing the ‘editing’ class and setting ‘contentEditable’ to ‘false.’

    reset() {
      this.editing = false;
      this.renderer.removeClass(this.el.nativeElement, "editing");
      this.renderer.setAttribute(this.el.nativeElement, "contentEditable", "false");
    }
    

    The final product

    Here’s the code all together. Hope you enjoy! https://gist.github.com/yokoishioka/f746f261a86c91ffb2a4ffdeef18a096

  • Combine the power of Angular services and directives to detect screen size changes

    Combine the power of Angular services and directives to detect screen size changes

    I’ve read a lot about how awesome Angular services can be (and they’re right) and have seen a few on directives (there should be way more), but what has really been exciting me lately is how to combine the two!

    Problem to solve

    Detect when the screen size changes from mobile, tablet or desktop to automatically toggle between a navigation menu and hamburger menu.

    Solution requirements

    1. Request and receive the values from anywhere in my Angular workspace without duplicating code or worrying about dependencies.
    2. Request the values from only the components who care about them.
    3. Receive the values as either numbers or device sizes, such as small, medium and large.
    4. Receive the values only if they change.

    Why not just use a service?

    To separate concerns and to be reusable, I created a toggle menu component that doesn’t care what content is inserted and is used by a menu component that only cares about creating the content. If I were to just use a service, both components would have to listen to the service even when the service wasn’t being used and the service would have to care about which state the component is in rather than just focusing on passing values.

    Why use a directive?

    Think of directives as being able to supplement your components without having to create another component to do it. It’s like having a phantom component that can do everything a component can do but without requiring an actual instance of itself. By not including the HostListener in the service, only the element that has the directive will be listening for screen size changes.

    Role breakdown

    Directive: Listens for when the screen width changes and sends those values to the service.

    Service: Receives values from anyone who uses it, categorizes the number by screen size (if requested) and sends values to whoever subscribes to them.

    Component: Tells the directive when to start listening for the changes. Subscribes to the service to get back the values, specifying it only cares when the values change. Determines when to stop sending values.

    The Directive

    Attach the selector to the element you want to listen for window events. You’ll want to include a HostListener for when the window first gets loaded and then also one if the screen gets resized. Then, send the values to the service.

    device-screen-size.directive.ts

    import { DevicesService } from './devices.service';
    
    @Directive({
    selector: '[cesDeviceScreenSize]'
    })
    export class DeviceScreenSizeDirective {
    
    constructor(
      private devices: DevicesService
    ) { }
    
    @HostListener('window:load', ['$event']) onLoad(event) {
      this.sendSize(event.currentTarget.innerWidth);
    }
    
    @HostListener('window:resize', ['$event']) onResize(event) {
      this.sendSize(event.currentTarget.innerWidth);
    }
    
    sendSize(width: number) {
      this.devices.setSize(width);
    }
    }
    

    The Service

    Inside of your service, you’ll want to use an input that will take in the values and pass those values to a subject so that anyone can subscribe to them, even if they aren’t using the directive.

    devices.service.ts

    import { Injectable, Input } from '@angular/core';
    import { Subject, Observable } from 'rxjs';
    import { ScreenSize, DetectType } from './devices';
    
    @Injectable({
      providedIn: 'root'
    })
    
    export class DevicesService {
      screenSize: Subject<any> = new Subject();
      @Input() detectSize: DetectType;
    
      constructor() { }
    
      setSize(width: number): Observable<any> {
        if (this.detectSize === 'number') {
          this.screenSize.next(width);
        }
    
        else {
          if (width < ScreenSize.medium) {
            this.screenSize.next('small');
          }
    
          else if (width > ScreenSize.medium) {
            this.screenSize.next('medium');
          }
    
          if (width > ScreenSize.large) {
            this.screenSize.next('large');
            }
          }
    
      return this.screenSize;
      }
    }
    

    The Component

    For my use case, I created a menu toggle component that needs to detect when the screen size changes so that it knows whether or not to display the hamburger icon instead of the full navigation menu. I specified to the service that I want to listen for device sizes rather than the screen size width because I only care when the device changes.

    I recently discovered the distinctUntilChanged RxJS operator that will only pass the value if it’s changed! How cool is that?? No more useless values clogging up my stream.

    I don’t have to listen for when the device is large because nothing changes between medium and large. And medium always comes in between small and large.

    Don’t forget to unsubscribe from the subject to avoid memory leaks.

    toggle-menu.component.ts

    import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core';
    import { DevicesService } from 'projects/devices/src/public-api';
    import { distinctUntilChanged } from 'rxjs/operators';
    import { Subscription } from 'rxjs';
    
    @Component({
      selector: 'ces-menu-toggle',
      templateUrl: './menu-toggle.component.html',
      styleUrls: ['./menu-toggle.component.scss']
    })
    
    export class MenuToggleComponent implements OnInit, OnDestroy {
      @Input() showMenuButton: boolean = false;
      @Input() showMenuContent: boolean = false;
      @Input() menuTitle: string = "menu";
      allowToggle: boolean;
      sub: Subscription;
    
      constructor(
        private devices: DevicesService
      ) { }
    
      ngOnInit() {
        this.devices.detectSize = "type";
        this.sub =      this.devices.screenSize.pipe(distinctUntilChanged()).subscribe(size => {
        this.checkSize(size);
      })
     }
    
      ngOnDestroy() {
        this.sub.unsubscribe();
      }
    
      closeMenu() {
        this.showMenuButton = true;
        this.showMenuContent = false;
      }
      
      openMenu() {
        this.showMenuButton = false;
        this.showMenuContent = true;
      }
    
      checkSize(size: string) {
        if (size === 'medium') {
          this.allowToggle = false;
          this.openMenu();
        }
    
        else if (size === 'small') {
          this.allowToggle = true;
          this.closeMenu();
        }
      }
    
      toggleNav() {
        if (this.allowToggle) {
          this.showMenuButton = !this.showMenuButton;
          this.showMenuContent = !this.showMenuContent;
         }  
      }
    }
    

    I also posted the code to GitHub Gist for better readability. You can view the menu in action on my blog’s website. (Expand and shrink the window after you click the link.) Thanks for reading!

  • Celebrate independence with a completely self-sufficient Angular button that can hide any parent

    Celebrate independence with a completely self-sufficient Angular button that can hide any parent

    One of the most fun things about Angular – yes, I said fun AND I’m not being sarcastic – is coming up with ways to create components that are totally modularized and can perform functions with a plug-and-play attitude. For example, I created a button that can hide any parent just by including its component within the component you’re trying to hide.

    Start with a generic button component

    At the heart of every good component are the absolute bare-minimum basics. If you want to take advantage of a modular approach, make sure you are using building blocks that are flexible enough to build upon and that are loosely coupled so that they won’t become restrictive later on. In other words, you want to build components that you can confidently change because you know those changes will be isolated and not cause unintended ripple effects.

    So, the first step is to create a button component that will be the foundation of all of your other buttons. For example, below is the base button component I use for my blog’s website. It gives the developer an option to add whatever content they want for the button, as indicated by Angular’s “ng-content” tag.

    button.component.html

    <button [ngClass]="class" [disabled]="disabled">
      <ng-content></ng-content>
    </button>
    

    Add CSS to your button

    button.component.scss

      @import "src/assets/styles/variables.scss";
    
      button {
        font-size: inherit;
        background: inherit;
        color: inherit;
        text-align: center;
        padding: 0.5em 1.0em;
        border: 1px solid;
        border-radius: $border-radius;
      }
      button:hover {
        filter: brightness(125%);
        cursor:pointer;
      }
    
      button.large {
        padding: 1.0em 2.0em;
        font-size: 2.0em;
        border: $border;
        border-radius: $border-radius;
      }
    
      button.no-border {
        border:0;
      }
    
      button.no-padding {
        padding:0;
      }
    
      .icon {
        width: 3.0em;
        height: 3.0em;
      }
    
      button:disabled {
        opacity: 0.25;
      }
    

    The developer can disable the button by setting the disabled property to true. I added CSS to make the disabled button have an opacity of 0.25 so that it appears disabled as well.

    Instead of hardcoding a color for the hover state, I am setting a filter to make the brightness at 125% so that it can apply to any button no matter what color it is. I also added the cursor to change to the hand, or more precisely pointer, upon hover since that’s not a default behavior.

    The developer can also choose to not have a border or padding.

    button.component.ts

      import { Component, Input } from '@angular/core';
    
      @Component({
        selector: 'ces-button',
        templateUrl: './button.component.html',
        styleUrls: [
          './button.component.scss'
        ]
      })
      
      export class ButtonComponent {
        @Input() class:string;
        @Input() disabled:boolean;
       }
    

    Next, create the button that can hide its parent

    I added inputs for the user to pass the label and opt for the default icon if desired. (This is useful if the developer wants to use the styles that are applied to the button, e.g., font size, padding, icon size, etc.)

    button-hide-parent.component.html

      <ces-button type="button" class="no-border no-padding" (click)="onClose()">
        <span *ngIf="label" class="label">{{ label }}</span>
        <ces-svg-x-circle class="button-close-icon"></ces-svg-x-circle>
      </ces-button>
    

    button-hide-parent.component.ts

      import { Component, Input, Renderer2, ElementRef, Output, EventEmitter } from '@angular/core';
    
      @Component({
        selector: 'ces-button-hide-parent',
        templateUrl: './button-hide-parent.component.html',
        styleUrls: ['./button-hide-parent.component.scss']
      })
      export class ButtonHideParentComponent  {
        @Input() icon: string;
        @Input() label:string;
        @Output() hidden: EventEmitter<any> = new EventEmitter();
    
        constructor(
          private renderer:Renderer2,
          private elem:ElementRef
        ) { }
    
      onClose() {
        const parent = this.renderer.selectRootElement(this.elem.nativeElement.parentNode);
        this.renderer.setStyle(parent, 'display', 'none')
        this.hidden.emit();
      }
    }
    

    By creating an instance of ElementRef, I’m able to easily obtain a reference to the component and therefore get the component’s parent without having to query the DOM. By using Renderer2, I’m able to safely target the parent component and apply the style “display:none.”

    I also added an EventEmitter just in case the parent needs to know that the parent was hidden.

    Use the new button

    To use the new button, include the selector in the HTML template of the component you would like to hide. No additional code is required to make it work.

    Note: You can specify another click event handler on the button in case you want to add another function in the parent’s typescript file. By doing so, clicking the button will invoke both the child’s and parent’s methods.