If you are using Angular Material Dialogs for example and when it pops up the page seems to move a bit, well that might be due to the scrolling strategy that the dialog is using. If you don’t want to see this annoying effect you can use the NoopScrollStrategy as ScrollStrategy, thus there is no influence on scrolling when the angular material dialog is opened.

One way to do that, is to set it via ScrollStrategyOptions to replace the default behaviour. First define a scrollStrategy variable and set the value to NoopScrollStrategy by calling the noop() of the ScrollStrategyOptions in OnInit:

export class AppComponent implements OnInit {
  scrollStrategy: ScrollStrategy;
  constructor(private keycloakService: KeycloakService,
              //...
              private historyDialog: MatDialog,
              private loginDialog: MatDialog,
              private loginDialogHelperService: LoginDialogHelperService,
              private readonly  scrollStrategyOptions: ScrollStrategyOptions) {}

  ngOnInit(): void {
      this.scrollStrategy = this.scrollStrategyOptions.noop();
  }

Then in the dialog configuration (MatDialogConfig) set the scrollStrategy property to this component variable

  • dialogConfig.scrollStrategy = this.scrollStrategy; :
  @HostListener('window:keydown.control.h', ['$event'])
  showHistory(event: KeyboardEvent) {
    if (!this.userIsLoggedIn) {
      const dialogConfig = this.loginDialogHelperService.loginDialogConfig('You need to be logged in to see the History Bookmarks popup');

      this.loginDialog.open(LoginRequiredDialogComponent, dialogConfig);
    } else {
      event.preventDefault();
      const dialogConfig = new MatDialogConfig();

      dialogConfig.disableClose = false;
      dialogConfig.autoFocus = true;
      dialogConfig.width = this.getRelativeWidth();
      dialogConfig.height = this.getRelativeHeight();
      dialogConfig.scrollStrategy = this.scrollStrategy;
      dialogConfig.data = {
        bookmarks$: this.userDataHistoryStore.getAllHistory$(this.userId),
        title: '<i class="fas fa-history"></i> History'
      };

      const dialogRef = this.historyDialog.open(HotKeysDialogComponent, dialogConfig);
      dialogRef.afterClosed().subscribe(
        data => {
          console.log('Dialog output:', data);
        }
      );
    }
  }

For the login required dialog, this option is injected via a service - const dialogConfig = this.loginDialogHelperService.loginDialogConfig('You need to be logged in to see the History Bookmarks popup');, but it follows basically the same principle:

@Injectable()
export class LoginDialogHelperService {

  scrollStrategy: ScrollStrategy;

  constructor(private readonly scrollStrategyOptions: ScrollStrategyOptions) {
    this.scrollStrategy = this.scrollStrategyOptions.noop();
  }

  loginDialogConfig(message: string) {
    const dialogConfig = new MatDialogConfig();

    dialogConfig.disableClose = true;
    dialogConfig.autoFocus = true;
    dialogConfig.scrollStrategy = this.scrollStrategy;
    dialogConfig.data = {
      message: message
    };

    return dialogConfig;
  }
}

Reference - https://material.angular.io/cdk/overlay/api#ScrollStrategyOptions


Shared with from Codever. 👉 Use the Copy to mine functionality to copy this snippet to your own personal collection and easy manage your code snippets.

Codever is open source on Github ⭐🙏

Configuring application environments

First define different environment files for the environments you might have under the project’s src/environments/ folder. Below there is an extract from environment.ts (used for development) and environment.prod.ts used for production:

//environment.ts
export const environment = {
  production: false,
  HOST: 'http://localhost:4200',
  API_URL: 'http://localhost:3000/api',
};

//environment.prod.ts
export const environment = {
  production: true,
  HOST: 'https://www.codever.dev',
  API_URL:  'https://www.codever.dev/api',
};

The build command (and ng serve) uses environment.ts as the build target when no environment is specified.

The main CLI configuration file, angular.json, contains a fileReplacements section in the configuration for each build target, which lets you replace any file in the TypeScript program with a target-specific version of that file. This is useful for including target-specific code or variables in a build that targets a specific environment, such as production or staging.

By default, no files are replaced. You can add file replacements for specific build targets. For example

"configurations": {
  "production": {
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.prod.ts"
      }
    ],
    

This means that when you build your production configuration with ng build --configuration production, the src/environments/environment.ts file is replaced with the target-specific version of the file, src/environments/environment.prod.ts.

You can add additional configurations (e.g. pro different stages) as required.

Using environment-specific variables in your app

Then in your code import environment and use the values defined in the files above, as in the following example:

import { environment } from '../../../environments/environment'
import { localStorageKeys } from '../model/localstorage.cache-keys';

@Injectable()
export class SystemService {
  cachedQueries = {
    PUBLIC_TAGS_LIST: `${environment.API_URL}/public/bookmarks`
  }

 //...
}

If you want to use environment variables in your angular html template, then just define a local variable for the imported environment and use it in the template, like in the following example:

import { environment } from 'environments/environment';

@Component({
  selector: 'app-social-share-dialog',
  templateUrl: './social-share-dialog.component.html',
  styleUrls: ['./social-share-dialog.component.scss'],
  providers: [DatePipe]
})
export class SocialShareDialogComponent implements OnInit {

  sharableId$: Observable<any>;
  readonly environment = environment;
  //...
}

The html part, where HOST value is used:

    <div>
      <h3>Shareable</h3>
      
    </div>

Reference - https://angular.io/guide/build#using-environment-specific-variables-in-your-app


Shared with from Codever. 👉 Use the Copy to mine functionality to copy this snippet to your own personal collection and easy manage your code snippets.

Codever is open source on Github ⭐🙏

Going “green”

As mentioned How I manage my dev bookmarks to save time and nerves I use heavily Codever to manage my bookmarks and code snippets, so there is always a browser tab open with the website. Lately I’ve also been developing quite a bunch of new features on the project. Lots of testing happens in the browser, and until now there was no quick way to differentiate between the production and development version, unless I look at the URL in the browser’s navigation bar of course.

Because I am always looking for things to make me life easier, I’ve remembered of an old trick to help me much easier differentiate between the two versions:

  • change the color of the navigation bar
  • change the favicon

Both are now green for the dev environment 🤓🟢

See in the pictures below how easy it’s to recognise the versions without looking the url

Recognise dev tab when in production
Recognise dev tab when in production
Recognise prod tab when in development
Recognise prod tab when in development

Code changes

Favicon

The code changes were minor. First change the favicon href attribute when not production

export class AppComponent implements OnInit {
    favIcon: HTMLLinkElement = document.querySelector('#favicon');
    readonly environment = environment;

    ngOnInit(): void {
        if (environment.production === false) {
            this.favIcon.href = 'assets/logo/logo-green.svg';
        }
    }
}

See Configure and use environment specific values in Angular and html template to see how to work with Angular environments

Then set the navigation’s bar color to green also based on not production condition

<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top shadow"
     [ngClass]="{'navbar-dev' : environment.production === false}" >

The navbar-dev css class

.navbar-dev {
  background-color: darkgreen !important;
}

See Conditional css class in angular html element on how to change a class in Angular dynamically

Multiple dev/testing environments

Of course if you have more dev/test environments like integration or preprod, consider differentiating them as well, your developers will thank you.

You won’t believe how these little tweaks can improve your development experience 💪

Use the ngClass input attribute with the object notation, where the keys are CSS classes that get added when the expression given in the value evaluates to truthy value, otherwise they are removed. In the example below the class navbar-dev-green is added to the nav element only for the dev environment (non-prod):

<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top shadow"
     [ngClass]="{'navbar-dev-green' : environment.production === false}" >
...
</nav>

Project: codever - File: navigation.component.html

To add multiple classes you can list them with space between them. To add different conditions for each class use a boolean expression evaluator for each class as shown in the example of the documentation:

<some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
<some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>

Reference - https://angular.io/api/common/NgClass


Shared with from Codever. 👉 Use the Copy to mine functionality to copy this snippet to your own personal collection and easy manage your code snippets.

Codever is open source on Github ⭐🙏

Use the expanded attribute of the mat-expansion-panel element and set it to true when the condition is met. In the following example a filter pipe is used and the result is placed in the filteredBookmarks variable which is checked in the condition - [expanded]="filteredBookmarks.length === 1" :

<mat-expansion-panel
  *ngFor="let bookmark of bookmarks | bookmarkFilter: filterText as filteredBookmarks; index as i"
  [expanded]="filteredBookmarks.length === 1">
  <mat-expansion-panel-header *ngIf="i<15">
    <div class="p-3">
      <h5 class="card-title">
        <a href=""
           [innerHTML]="bookmark.name | slice:0:100 | highlightHtml: filterText"
           target="_blank"
           (click)="addToHistoryService.promoteInHistoryIfLoggedIn(true, bookmark)"
           (auxclick)="addToHistoryService.onMiddleClickInDescription(true, $event, bookmark)"
        >
          see innerhtml
        </a>
        <sup class="external-link-hint"><i class="fas fa-external-link-alt"></i></sup>
      </h5>
      <h6 class="card-subtitle mb-2 text-muted url-under-title"
          [innerHTML]="bookmark.location | slice:0:120 | highlightHtml: filterText"
      >
        see innerhtml
      </h6>
    </div>
  </mat-expansion-panel-header>

  <ng-template matExpansionPanelContent>
    <app-bookmark-text [bookmark]="bookmark" [showMoreText]="true"
                       (click)="addToHistoryService.onClickInDescription(true, $event, bookmark)"
                       (auxclick)="addToHistoryService.onMiddleClickInDescription(true, $event, bookmark)">
    </app-bookmark-text>
  </ng-template>
</mat-expansion-panel>

Project: codever - File: hot-keys-dialog.component.html

See it in action at www.codever.dev:

Copy-to-clipboard-demo


Shared with from Codever. 👉 Use the Copy to mine functionality to copy this snippet to your own personal collection and easy manage your code snippets.

Codever is open source on Github ⭐🙏