الزاوية 9. إعادة تشغيل حراس الصفحة الحالية. حراس المسار الحالي الزناد

واجهت الحاجة إلى إعادة تشغيل الحراس للصفحة الحالية بغض النظر عن الصفحة المفتوحة.



لم أجد حلاً قياسيًا ، وتلك المعروضة على الإنترنت تقتصر على صفحة واحدة. لذلك ، كتبت رسالتي الخاصة وقررت مشاركتها.



وصف حالة



صفحات التطبيق مقسمة إلى 3 مجموعات:



  • للمستخدمين المصرح لهم فقط

  • للمستخدمين غير المصرح لهم فقط

  • لأي مستخدمين



يمكنك تسجيل الدخول أو تسجيل الخروج من أي صفحة.



إذا قمت بالدخول / الخروج من صفحة ذات وصول محدود ، فأنت بحاجة إلى الانتقال إلى الصفحة المسموح بها.



إذا كانت الصفحة لا تحتوي على قيود ، فأنت بحاجة إلى البقاء في الصفحة الحالية.



لفهم أفضل ، يُنصح بمعرفة:





القرار



يتم تنفيذ التفريق بين حقوق الوصول إلى الصفحات من خلال guard - CanActivate. يتم إجراء الفحص عند الانتقال إلى الصفحة ، ولكنه لا يتفاعل مع التغييرات في الحقوق عندما يكون الانتقال قد تم بالفعل. لتجنب تكرار منطق حقوق الوصول ، نقوم بإعادة تشغيل الحراس بالقوة.



تتم إعادة تشغيل أدوات الحماية للصفحة الحالية من خلال التنقل في عنوان url الحالي. وتغييرات في الإستراتيجيات Router.onSameUrlNavigation و Route.runGuardsAndResolvers.



إليك حل جاهز. مزيد من التفاصيل في القسم التالي.
  import { Injectable } from '@angular/core';
  import { ActivatedRoute, PRIMARY_OUTLET, Router, RunGuardsAndResolvers } from '@angular/router';

  @Injectable()
  export class GuardControlService {
    constructor(
      private route: ActivatedRoute,
      private router: Router,
    ) {}

    /**
    *   guard-  url
    */
    forceRunCurrentGuards(): void {
      //   Router.onSameUrlNavigation       url
      const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');

      //   ActivatedRoute  primary outlet
      const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);

      //   runGuardsAndResolvers  ActivatedRoute          url
      const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');

      //   
      this.router.navigateByUrl(
        this.router.url
      ).then(() => {
        //  onSameUrlNavigation
        restoreSameUrl();
        //  runGuardsAndResolvers
        restoreRunGuards();
      });
    }

    /**
    *  onSameUrlNavigation    
    * @param router - Router,    
    * @param strategy -  
    * @return callback   
    */
    private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {
      const onSameUrlNavigation = router.onSameUrlNavigation;
      router.onSameUrlNavigation = strategy;

      return () => {
        router.onSameUrlNavigation = onSameUrlNavigation;
      }
    }

    /**
    *   route  outlet-
    * @param route - Route    
    * @param outlet -  outlet-,    
    * @return  ActivatedRoute   outlet
    */
    private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {
      if (route.children?.length) {
        return this.getLastRouteForOutlet(
          route.children.find(item => item.outlet === outlet),
          outlet
        );
      } else {
        return route;
      }
    }

    /**
    *  runGuardsAndResolvers  ActivatedRoute   ,    
    * @param route - ActivatedRoute    
    * @param strategy -  
    * @return callback   
    */
    private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {
      const routeConfigs = route.pathFromRoot
        .map(item => {
          if (item.routeConfig) {
            const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;
            item.routeConfig.runGuardsAndResolvers = strategy;
              return runGuardsAndResolvers;
              } else {
            return null;
          }
        });

      return () => {
        route.pathFromRoot
          .forEach((item, index) => {
            if (item.routeConfig) {
              item.routeConfig.runGuardsAndResolvers = routeConfigs[index];
            }
          });
      }
    }
  }
  




وصف الحل الإضافي



أول شيء تريد محاولة إعادة تشغيل الحراس هو استخدام التنقل إلى عنوان url الحالي.



this.router.navigateByUrl(this.router.url);


ولكن ، بشكل افتراضي ، يتم تجاهل حدث انتقال عنوان url الحالي ولا يحدث شيء. لكي يعمل هذا ، تحتاج إلى تكوين التوجيه.



إعداد التوجيه



1. قم بتغيير استراتيجية جهاز التوجيه. onSameUrlNavigation



يمكن أن يأخذ onSameUrlNavigation القيم التالية:



onSameUrlNavigation: 'reload' | 'ignore';


من أجل الحساسية للانتقال إلى عنوان url الحالي ، تحتاج إلى تعيين "إعادة التحميل".



لا يتم إعادة تحميل تغيير السياسة ، ولكنه ينشئ حدث تنقل إضافي. يمكن الحصول عليها من خلال الاشتراك:



this.router.events.subscribe();


2. تغيير استراتيجية الطريق. runGuardsAndResolvers



يمكن أن تأخذ runGuardsAndResolvers القيم التالية:



type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always' | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean);


من أجل الحساسية للانتقال إلى عنوان url الحالي ، تحتاج إلى تعيين "دائمًا".



تكوين التوجيه أثناء تكوين التطبيق



onSameUrl الملاحة:



const routes: : Route[] = [];
@NgModule({
  imports: [
    RouterModule.forRoot(
      routes,
      { onSameUrlNavigation: 'reload' }
    )
  ]
})


runGuardsAndResolvers:



const routes: Route[] = [
  {
    path: '',
    component: AppComponent,
    runGuardsAndResolvers: 'always',
  }
];


تكوين التوجيه في وقت التشغيل



constructor(
  private router: Router,
  private route: ActivatedRoute
) {
  this.router.onSameUrlNavigation = 'reload';
  this.route.routeConfig.runGuardsAndResolvers = 'always';
}


إعادة تشغيل الحراس



لإعادة تشغيل أدوات الحماية لصفحة معينة ، يكفي تكوين التوجيه أثناء التكوين.



ولكن لإعادة تحميل أدوات الحماية لأي صفحة ، سيؤدي تغيير runGuardsAndResolvers في كل مسار إلى عمليات فحص غير ضرورية. والحاجة إلى تذكر هذه المعلمة دائمًا - للأخطاء.



نظرًا لأن حالتنا تفترض إعادة تشغيل أي صفحة دون قيود في إعداد التطبيق ، فأنت بحاجة إلى:



1. استبدل onSameUrlNavigation واحتفظ بالقيمة الحالية



//   Router.onSameUrlNavigation       url
const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');

...
/**
*  onSameUrlNavigation    
* @param router - Router,    
* @param strategy -  
* @return callback   
*/
private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {
  const onSameUrlNavigation = router.onSameUrlNavigation;
  router.onSameUrlNavigation = strategy;

  return () => {
    router.onSameUrlNavigation = onSameUrlNavigation;
  }
}


2. احصل على ActivatedRoute لعنوان url الحالي



نظرًا لأنه يتم تنفيذ ActivatedRoute للحقن في الخدمة ، فإن ActivatedRoute الناتجة غير مرتبطة بعنوان url الحالي.



ActivatedRoute لعنوان url الحالي يقع في آخر منفذ أساسي وتحتاج إلى العثور عليه:



//   ActivatedRoute  primary outlet
const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);

...
/**
*   route  outlet-
* @param route - Route    
* @param outlet -  outlet-,    
* @return  ActivatedRoute   outlet
*/
private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {
  if (route.children?.length) {
    return this.getLastRouteForOutlet(
      route.children.find(item => item.outlet === outlet),
      outlet
   );
  } else {
    return route;
  }
}


3. استبدل runGuardsAndResolvers لجميع ActivatedRoute وأسلافها ، مع الاحتفاظ بالقيم الحالية



يمكن وضع حارس يقيد الوصول في أي من أسلاف ActivatedRoute الحالية. تقع جميع الأجداد في pathFromRoot.



//   runGuardsAndResolvers  ActivatedRoute          url
const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');

...
/**
*  runGuardsAndResolvers  ActivatedRoute   ,    
* @param route - ActivatedRoute    
* @param strategy -  
* @return callback   
*/
private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {
  const routeConfigs = route.pathFromRoot
    .map(item => {
      if (item.routeConfig) {
        const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;
        item.routeConfig.runGuardsAndResolvers = strategy;
        return runGuardsAndResolvers;
      } else {
        return null;
      }
    });

  return () => {
    route.pathFromRoot
      .forEach((item, index) => {
        if (item.routeConfig) {
          item.routeConfig.runGuardsAndResolvers = routeConfigs[index];
        }
      });
  }
}


4. انتقل إلى عنوان url الحالي



this.router.navigateByUrl(this.router.url);


5. أعد runGuardsAndResolvers و onSameUrlNavigation إلى حالتهما الأصلية



restoreRunGuards();
restoreSameUrl();


6. الجمع بين المراحل في وظيفة واحدة



constructor(
  private route: ActivatedRoute,
  private router: Router,
) {}

/**
*   guard-  url
*/
forceRunCurrentGuards(): void {
  //   Router.onSameUrlNavigation       url
  const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');

  //   ActivatedRoute  primary outlet
  const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);

  //   runGuardsAndResolvers  ActivatedRoute          url
  const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');

  //   
  this.router.navigateByUrl(
    this.router.url
  ).then(() => {
    //  onSameUrlNavigation
    restoreSameUrl();
    //  runGuardsAndResolvers
    restoreRunGuards();
  });
}





آمل أن تكون هذه المقالة مفيدة لك. إذا كانت هناك حلول أخرى ، فسأكون سعيدًا برؤيتها في التعليقات.



All Articles