How to Make Angular Universal Behave Like NextJS
Tags: angular, webdev, nextjs
Published: 2022-10-26 12:45:27

Can we really get all the benefits of NextJS in Angular Universal?. It is arguably hard to learn, especially when compared to newbie frameworks like SvelteKit, but it is **still** incredibly powerful. Let's see what's possible.

# 1. GetServerSideProps

First thing is first, let's handle the state transfer from the server to the browser. Create a new service like so:

```
ng g s shared/state
```

**state.service.ts**
```ts
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { makeStateKey, TransferState } from '@angular/platform-browser';

@Injectable({
  providedIn: 'root'
})
export class StateService {

  isBrowser: Boolean;
  isServer: Boolean;

  constructor(
    @Inject(PLATFORM_ID) platformId: Object,
    private transferState: TransferState
  ) {
    this.isBrowser = isPlatformBrowser(platformId);
    this.isServer = isPlatformServer(platformId);
  }

  saveState<T>(key: string, data: any): void {
    this.transferState.set<T>(makeStateKey(key), data);
  }

  getState<T>(key: string, defaultValue: any = []): T {
    const state = this.transferState.get<T>(makeStateKey(key), defaultValue);
    this.deleteState(key);
    return state;
  }

  hasState<T>(key: string): boolean {
    return this.transferState.hasKey<T>(makeStateKey(key));
  }

  deleteState<T>(key: string): void {
    this.transferState.remove<T>(makeStateKey(key));
  }

  async loadState<T>(promise: Promise<T>, key: string): Promise<T> {
    if (this.isBrowser && this.hasState<T>(key)) {
      return this.getState<T>(key);
    }
    const data = await promise;
    if (this.isServer) {
      this.saveState<T>(key, data);
    }
    return data;
  }
}
```

This is boilerplate code, you should NEVER have to change it. You also have access to the functions to check for server or browser.

Next, generate a new component with a resolver. The resolver is what you use to load items before a component, but only from the router.

```sql
ng g r my-resolver
```

Make sure to use that resolver in your router:

**my-router.module.ts**

```js
const routes: Routes = [
  { path: '', component: MyComponent, resolve: { props: MyResolver } }
];
```

Import the state transfer service, create a **getServerSideProps** function using the **loadState** function. This will automatically pass the information from the server to the browser. The **loadState** function also uses the url as the key here, but you could use anything. This function is reusable in many contexts.

**my.resolver.ts**
```js
...
@Injectable({
  providedIn: 'root'
})
export class UsernameResolver implements Resolve<any> {

  constructor(
    private ss: StateService,
    private router: Router,
    ...
  ) { }

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Promise<any> {
    return this.ss.loadState(
      this.getServerSideProps({ urlQuery: route.params }),
      state.url
    );
  }

  async getServerSideProps({ urlQuery }: { urlQuery: any }) {

    const { slug } = urlQuery;
    ...

    return { user, posts };
}
```
Import the data into your component:

**my.component.ts**
```js
...
export class MyComponent {

  posts: any;
  user: any;

  constructor(private route: ActivatedRoute) {
    const { posts, user } = this.route.snapshot.data['props'];
    this.posts = posts;
    this.user = user;
  }
}
```

# 2. GetStaticProps / GetStaticPaths

So there is no direct translation here, but you could write your own. For caching, you could use an in-memory cache or a file system cache.

- [ngx-isr](https://github.com/eneajaho/ngx-isr)

However, what you really want is a [cache-control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header. What **GetStaticPaths** really does is fetch the names of the paths, and cache them once. Then, when you run your router live, it uses that cached data to decide which other paths should get cached etc. It is simple and incredibly complicated at the same time. 

I personally find it more valuable to just cache all pages the first time they load (particularly in a blog), and have them un-cache when the data gets changed. This is easy in NextJS, but would require custom programming here. I have not written this yet, but I will save the specifics of this for another post.

- [Vercel](https://vercel.com/docs/concepts/edge-network/caching#serverless-functions)
- [Netlify](https://www.netlify.com/blog/2017/02/23/better-living-through-caching/)
- [Firebase Functions](https://firebase.google.com/docs/hosting/manage-cache)
- [Cloud Run](https://cloud.google.com/cdn/docs/caching)

# 3. One File per Component

I personally do not like JSX, but functional components have a huge advantage over classes: they are tree-shakable. That being said, we can combine all of our styles, ts, and html into one file. This is especially good for small components. Modern VS Code also handles the inline html as expected with validation, this used to not be the case.

Change your component generation:

```sql
ng g c test --inline-style=true --inline-template=true
```

Or permanently:

**angular.json**
```js
  "projects": {
    "some-project-name": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "inlineTemplate": true,
          "inlineStyle": true,
        }
```

# 4. No More Modules

Angular now has [standalone components](https://angular.io/guide/standalone-components), which allow you to skip modules use all together:

```
ng g c test
```
Add the **standalone: true** to the component, and use the `imports` to import modules etc. Okay, so for now we can't completely get rid of modules since things like angular material still use them. I suspect this will change within two years.

**test.component.ts**
```js
@Component({
  standalone: true,
  selector: 'app-test',
  imports: [ImageComponent],
  template: `
    ... <br><div class="something"...
  `,
})

```

# 5. Add API Endpoints

Add an endpoint to your **server.ts** file:

```ts
import { handler } from 'handler';
...

server.get('/api/**', handler);
```

I wrote a whole post on this [here](https://dev.to/jdgamble555/angular-universal-rest-api-endpoints-23fj).

So not everything is perfect, but we are just ultimately using JavaScript under-the-hood. Svelte, Vue, or SolidJS is very quick (but not Qwik), but Angular and React are, in fact, pretty comparable these days. NextJS just build something better over React.

Let's push Angular Universal to its limit.

J