Best way to extract inline JavaScript from Razor view to external TypeScript file

Hey folks, I’m trying to clean up my Razor view by moving the inline JavaScript to an external TypeScript file. The code is pretty specific to this view and relies on lots of HTML elements. Here’s what I’ve got so far:

namespace MyApp {
  export class ViewHandler {
    private elementRefs: { [key: string]: JQuery } = {};
    private dataId: number;

    constructor(config: any) {
      this.setup(config);
    }

    private setup(config: any) {
      // Set up stuff here
    }
  }
}

I’m calling it in my cshtml like this:

$(function() {
  new MyApp.ViewHandler({
    elements: {
      counter: $('#counter'),
      answers: $('#answers'),
      pager: $('#pager')
    },
    dataId: '@Model.DataId'
  });
});

I’m worried that even though the Razor view looks cleaner now, the TypeScript file still has a bunch of ties to the HTML. Am I on the right track here? Should I change the HTML too? And what about the init code in the cshtml file - leave it or move it? Any tips on best practices for this kind of refactoring would be awesome. Thanks!

Your approach is solid, but there’s room for improvement. Consider using a module pattern instead of a namespace. This can help with encapsulation and avoid polluting the global scope.

For the HTML ties, you could use custom data attributes as selectors. This decouples your TypeScript from specific IDs or classes.

As for the init code, I’d keep a minimal script in the cshtml to load and initialize your module. The bulk of the setup should be in your TypeScript file.

Here’s a rough idea:

export class ViewHandler {
  constructor(private config: ViewConfig) {
    this.init();
  }

  private init(): void {
    // Setup logic here
  }
}

export function initialize(config: ViewConfig): void {
  new ViewHandler(config);
}

Then in your cshtml:

<script type="module">
  import { initialize } from './viewHandler.js';
  initialize({ dataId: @Model.DataId });
</script>

This approach maintains separation of concerns while keeping your view clean and your TypeScript modular.

I’ve been through a similar process recently, and I can share what worked for me. Your approach is on the right track, but there are a few tweaks you could consider.

First, I’d suggest using data attributes in your HTML elements instead of IDs. This way, your TypeScript can select elements without being tightly coupled to specific IDs. For example:

<div data-counter></div>
<div data-answers></div>
<div data-pager></div>

Then in your TypeScript:

private setup(config: any) {
  this.elementRefs = {
    counter: $('[data-counter]'),
    answers: $('[data-answers]'),
    pager: $('[data-pager]')
  };
}

As for the init code, I prefer moving it to TypeScript as well. You can create a static method in your ViewHandler class:

static init(dataId: number) {
  $(function() {
    new MyApp.ViewHandler({
      dataId: dataId
    });
  });
}

Then in your Razor view, you’d just have:

<script>
  MyApp.ViewHandler.init(@Model.DataId);
</script>

This approach has worked well for me in keeping the view clean and the TypeScript more modular.

yo, ur approach looks decent but i got a tip. try using custom events to decouple ur typescript from the html. like this:

document.dispatchEvent(new CustomEvent(‘counter-update’, { detail: someData }));

then in ur ts:

document.addEventListener(‘counter-update’, (e) => {
// handle event
});

this way ur ts doesnt need to kno bout specific html elements. makes it way more flexible n easier to maintain.