发布于 2026-01-06 0 阅读
0

Angular 中的路由重构💿

Angular 中的路由重构💿

Remix团队正在构建一个基于 React Router 原理并使用 React 进行渲染的 Web 框架。他们还有更大的计划,将相同的路由功能移植到其他框架中。最近,该团队发布了一篇博文,宣布他们正在对React Router 进行 Remixing,即将 React Router 的底层组件提取出来,使其与框架无关,可供任何框架使用。本文将介绍我如何让 Remix Routing 与 Angular 协同工作。


太长不看

您可以在这里查看演示应用:https://remix-router-angular.netlify.app/

GitHub 仓库:https://github.com/brandonroberts/remix-router-angular


一切都始于一条推文……

Kent C. Dodds Remix Angular

一段时间过去了,我们得到了更多面包屑。

瑞安·弗洛伦斯

那这意味着什么呢?我深入研究了一下,果然,@remix-run/router它作为一个独立的软件包存在,用于处理管理路由器状态、浏览器历史记录等底层逻辑。

构建路由器服务

import { createBrowserRouter } from '@remix-run/router';

  const router = createBrowserRouter({
    routes,
  });

  router.initialize();
Enter fullscreen mode Exit fullscreen mode

几行代码就能在 Angular 中集成路由解决方案?🤔 别急。Remix Router 的确可以处理路由设置、路由状态管理、命令式导航等等,但它并不负责组件渲染。不过,Angular v14 大大简化了动态组件的渲染,所以我们最多只需要一个路由服务和一个出口组件就能开始使用了。

export const ROUTES = new InjectionToken<RouteObject[]>('ROUTES');

export const REMIX_ROUTER = new InjectionToken('Remix Router', {
  providedIn: 'root',
  factory() {
    const routes = inject(ROUTES);
    const router = createBrowserRouter({
      routes,
    });
    router.initialize();
    return router;
  },
});
Enter fullscreen mode Exit fullscreen mode

Remix Router 需要预先定义所有路由。上面的代码定义了一些注入令牌,以便我们可以注入提供的路由,并为路由服务创建浏览器路由器。

@Injectable({
  providedIn: 'root',
})
export class Router {
  private _remixRouter = inject(REMIX_ROUTER);
  routerState$ = new BehaviorSubject<RouterState>(this._remixRouter.state);

  constructor() {
    this._remixRouter.subscribe((rs) => this.routerState$.next(rs));
  }

  get state() {
    return this._remixRouter.state;
  }

  navigate(path: string, opts?: NavigateOptions) {
    this._remixRouter.navigate(path, opts);
  }
}

export function provideRoutes(routes: RouteObject[]) {
  return [{ provide: ROUTES, useValue: routes }];
}
Enter fullscreen mode Exit fullscreen mode

路由服务非常精简。它没有使用 Angular 的PlatformLocation任何Location服务,因为这些都由 Remix Router 处理。Remix Router 有一个subscribe监听路由状态变化的方法,所以我们将其封装成一个易于观察的对象,方便所有组件监听。

接下来是渲染组件的输出接口。

建造路由器插座

@Directive({
  selector: 'outlet',
  standalone: true,
})
export class Outlet {
  private destroy$ = new Subject();
  private cmp!: Type<any>;
  private context? = getRouteContext();
  private router = getRouter();
  private vcr = inject(ViewContainerRef);

  ngOnInit() {
    this.setUpListener();
  }

  setUpListener() {
    this.router.routerState$
      .pipe(
        tap((rs) => {
          const matchesToRender = this.getMatch(rs);
          const currentCmp = matchesToRender.route.element;

          if (this.cmp !== currentCmp) {
            this.vcr.clear();
            this.vcr.createComponent(currentCmp, {
              injector: this.getInjector(matchesToRender),
            });
            this.cmp = currentCmp;
          }
        }),
        takeUntil(this.destroy$)
      )
      .subscribe();
  }

  getInjector(matchesToRender: DataRouteMatch) {
    const injector = Injector.create({
      providers: [
        {
          provide: ROUTE_CONTEXT,
          useValue: {
            id: matchesToRender.route.id,
            index: matchesToRender.route.index === true,
            params: matchesToRender.params,
          },
        },
      ],
      parent: this.vcr.injector,
    });

    return injector;
  }

  getMatch(routerState: RouterState) {
    const { matches } = routerState;
    const idx = matches.findIndex(
      (match) => match.route.id === this.context?.id
    );
    const matchesToRender = matches[idx + 1];

    return matchesToRender;
  }

  ngOnDestroy() {
    this.destroy$.next(true);
  }
}
Enter fullscreen mode Exit fullscreen mode

出口是一个占位符指令,它监听路由状态的变化,并渲染与路由 ID 匹配的组件。Remix Router 知道所有路径,因此它会提供一个匹配项数组进行渲染。这使我们能够轻松处理嵌套路由。

可以使用指令定义父组件outlet以渲染子路由

@Component({
  selector: 'home',
  standalone: true,
  imports: [Outlet],
  template: `
    Parent - 
    <a (click)="child('child')">Child</a>

    <outlet></outlet>
  `
})
export class ParentComponent {
  router = getRouter();
}
Enter fullscreen mode Exit fullscreen mode

定义路由

现在我有了路由器和插座,可以注册一些路由了。

import { RouteObject } from 'remix-router-angular';

import {
  AboutComponent,
  loader as aboutLoader,
  action as aboutAction,
} from './about.component';
import { HomeComponent } from './home.component';
import { ParentComponent } from './parent.component';
import { ChildComponent } from './child.component';

export const routes: RouteObject[] = [
  { path: '/', element: HomeComponent },
  {
    path: '/parent',
    element: ParentComponent,
    children: [
      {
        path: ':child',
        element: ChildComponent,
      },
    ],
  },
  {
    path: '/about',
    element: AboutComponent,
    action: aboutAction,
    loader: aboutLoader,
  },
];
Enter fullscreen mode Exit fullscreen mode

about路由使用一个loader用于加载数据的组件和一个action用于处理表单数据的组件。它们的工作方式与 Remix 中现有的组件完全相同。

export const action: ActionFunction = async ({ request }) => {
  const formData = await request.formData();
  const name = formData.get('name');

  if (!name) {
    return {
      name: 'Name is required',
    };
  }

  return redirect(`/?name=${name}`);
};

export const loader: LoaderFunction = async () => {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  const todos = await res.json();

  return json({ todos });
};
Enter fullscreen mode Exit fullscreen mode

操作和加载器允许您为路由预取数据、处理表单验证、重定向等。

提供路线

因为我使用的是带有独立功能的 Angular v14,所以我使用了该bootstrapApplication函数,并通过该函数传递了一些提供程序provideRoutes

import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRoutes } from 'remix-router-angular';

import { AppComponent } from './app/app.component';
import { routes } from './app/routes';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

bootstrapApplication(AppComponent, {
  providers: [provideRoutes(routes)],
});
Enter fullscreen mode Exit fullscreen mode

我为使用新inject()功能从 Remix Router 获取操作/加载器数据以及其他部分进行了一些改进,但对于早期开发阶段来说,一切都运行得相当不错。

我很期待看到这个项目取得更多进展!Remix团队为此付出了辛勤努力,值得称赞。

您可以在这里查看演示应用:https://remix-router-angular.netlify.app/

GitHub 仓库:https://github.com/brandonroberts/remix-router-angular

了解更多

Remix
Angular
Angular v14 发布帖
remix-router-vue

如果你喜欢这篇文章,请点击❤️,让更多人看到。关注我的推特并订阅我的YouTube频道,获取更多关于AngularNgRx等内容!

文章来源:https://dev.to/brandontroberts/remix-ing-routing-in-angular-4g90