جاوااسکریپت - چگونه قبل از مقداردهی اولیه در انگولار/انگولار ۹ مسیرها را به صورت پویا ایجاد کنیم
من روی یک پروژه تبدیل برنامه AngularJS به Angular کار میکنم و با یک مانع در مورد مسیریابی روبهرو هستم.
خلاصه: من نیاز دارم مسیرها بر اساس پاسخ یک API قبل از استفاده از ماژول مسیریابی تعریف شوند.
سناریوی کاری در AngularJS: (نوعی شبهکد در ادامه)
چند مسیر پایه وجود دارد که برای همه وجود دارند، اینها به روش استاندارد AngularJS تعریف شدهاند:
/home
/settings
...etc
سپس مسیرهای پویا وجود دارند که بر اساس پاسخ یک API ایجاد میشوند
/purchase-requests
/invoices
/godzilla
...etc. Content doesn’t matter, basically, a dynamic list of routes that an existing API gives as an array of strings
گردش کار پایهای اپلیکیشن موجود AngularJS:
- اپلیکیشن AngularJS بلافاصله با استفاده از ng-app به یک عنصر متصل نمیشود، همانطور که معمولاً انجام میشود.
- یک پاسخ خام (یا jQuery) از API هنگام بارگذاری صفحه دریافت میشود.
- برنامه AngularJS با استفاده از موارد زیر مقداردهی اولیه میشود:
angular.bootstrap(document.getElementById('mainElementId'),[‘appName']);
این کار به دلیل رفتار AngularJS است که .config() را هنگام بارگذاری فراخوانی نمیکند بلکه هنگام بوتاسترپ شدن برنامه Angular فراخوانی میکند، که ما آن را تا بعد از دریافت پاسخ API به تعویق میاندازیم.
نمونه AngularJS که امروز کار میکند:
<script>
let appList = [];
const mainApp = angular.module('mainApp', ['ngRoute']);
// Controllers
mainApp.controller('mainController', mainController);
mainApp.controller('homeController', homeController);
mainApp.controller('appListController', appListController);
mainApp.controller('appSingleController', appSingleController);
mainApp.controller('errorController', errorController);
// config will not be called until the app is bootstrapped
mainApp.config(function($routeProvider) {
// Default routes that everyone gets
$routeProvider.when('/', { templateUrl: 'views/home.html', controller: 'homeController'});
$routeProvider.when('/home', { templateUrl: 'views/home.html', controller: 'homeController'});
// Add the dynamic routes that were retreived from the API
for (let appName in appList) {
$routeProvider.when(`/${appName}`, { templateUrl: 'views/app-list.html', controller: 'appListController'});
$routeProvider.when(`/${appName}/new`, { templateUrl: 'views/app-single.html', controller: 'appSingleController'});
$routeProvider.when(`/${appName}/view/:itemNumber`, { templateUrl: 'views/app-single.html', controller: 'appSingleController'});
}
$routeProvider.otherwise({ templateUrl: 'views/error.html', controller: 'errorController'});
});
$(document).ready(function() {
const options = {
type: 'GET',
url: '/api/apps/getAvailableApps',
success: onAppSuccess,
};
$.ajax(options);
});
function onAppSuccess(response) {
appList = response.appList;
angular.bootstrap(document.getElementById('mainApp'), ['mainApp']);
}
</script>
<!-- Typically, you bind to the app using ng-app="mainApp" -->
<div id="mainApp" class="hidden" ng-controller="mainController">
<!-- Route views -->
<div ng-view></div>
</div>
در Angular 9 (یا به نظر میرسد در هر نسخه اخیر دیگری از Angular)، مسیرها در ماژول مسیریابی قبل از مقداردهی اولیه کامپوننت اصلی تعریف میشوند:
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: '', component: DashboardComponent },
{ path: 'home', component: DashboardComponent },
{ path: 'settings', component: SettingsComponent },
];
استفاده از router.resetConfig کار نمیکند
فرض کنیم من ماژول اصلی را طوری تنظیم کنم که ابتدا تنظیمات API را بارگذاری کند، سپس بر اساس پاسخ از resetConfig استفاده کند. این روش عالی کار میکند اگر اولین صفحهای که یک کاربر بارگذاری میکند / یا /home یا یکی از مسیرهای از پیش تعریفشده دیگر باشد: مسیرهای داینامیک جدید ایجاد میشوند و ناوبری به آنها کار میکند.
با این حال، اگر کاربری مستقیماً به مسیری که از قبل تعریف نشده است برود (مثلاً /godzilla)، روتر حتی اجازه بارگذاری صفحه را نمیدهد یا اگر مسیر wildcard تنظیم شده باشد، صفحه 404 را نشان میدهد. متد ngOnInit() در کامپوننت اصلی (که من قصد داشتم از آن برای بارگذاری پاسخ API استفاده کنم) هرگز فرصتی برای اجرا پیدا نمیکند.
سؤال این است: چگونه میتوانم مسیرها را بر اساس پاسخ API قبل از اجرای ناوبری مسیر یا حتی قبل از مقداردهی اولیه ایجاد کنم؟
پاسخ شماره ۱
راهی که من مسیرهای پویا را اضافه میکنم این است که قالبهای URL مسیر را با استفاده از پارامترها از قبل تعریف کنم.
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: '', component: DashboardComponent },
{ path: 'home', component: DashboardComponent },
{ path: 'settings', component: SettingsComponent },
{ path: ':appName', canActivate: AppGuard, children: [
{ path: '', component: AppListComponent },
{ path: 'new', component: 'NewAppComponent' },
{ path: 'view/:itemNumber', component: AppSingleComponent }
] },
{ path: '**', component: ErrorComponent }
];
مسیرها به ترتیب مطابقت داده میشوند، بنابراین مسیرهای «شناختهشده» باید ابتدا قرار گیرند. هر URL با یک بخش که با مسیرهای «شناختهشده» مطابقت نداشته باشد، با :appName مطابقت داده میشود. شما میتوانید یک گارد اعلام کنید تا تأیید کند که پارامتر :appName معتبر است. اگر اینطور نباشد، مسیر '**' با آن مطابقت داده میشود.
نگهبان چیزی شبیه به این خواهد بود:
@Injectable({ providedIn: 'root' })
export class AppGuard implements CanActivate {
constructor(private appService: AppService) {
}
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
const appName = route.params.appName;
return this.appService.isValidAppName(appName);
}
}
جایی که appService.isValidAppName تابعی است که نام برنامه را اعتبارسنجی میکند.
پاسخ شماره ۲
چگونه میتوانم مسیرها را بر اساس پاسخ API قبل از اجرای ناوبری مسیر یا حتی قبل از مقداردهی اولیه آن ایجاد کنم؟
دو راه برای انجام این کار وجود دارد.
روش اول استفاده از Component برای نمایش تمام مسیرهای پویا است. همه مسیرهای ایستا ابتدا تعریف میشوند و در نهایت مسیرهای پویا به DynamicComponent با پارامتر مسیر id هدایت میشوند. در DynamicComponent، ما از ActivatedRoute برای دریافت پارامتر مسیر استفاده میکنیم و از Router برای هدایت به مسیر 404 در صورت شکست استفاده میکنیم.
در app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: "prefix" },
{ path: 'login', component: LoginComponent },
{ path: 'home', component: DashboardComponent },
{ path: 'settings', component: SettingsComponent },
{ path: '404', component: PageNotFoundComponent },
{ path: ':id', component: DynamicComponent },
];
در DynamicComponent
constructor(private aroute: ActivatedRoute, private router: Router) { }
ngOnInit(): void {
this.aroute.params.pipe(first()).subscribe((param) => {
console.log(param.id)
... // make any API call with param.id and get a response as promise
.then( (response) => {
... // do whatever you want to do on success
})
.catch( (error) => {
console.error(error);
this.router.navigate(['404']); // route to 404 on failure
})
}
}
راه دوم استفاده از Service برای فیلتر کردن تمام مسیرهای ناشناخته است. تمام مسیرهای ثابت ابتدا تعریف میشوند و در نهایت مسیرهای پویا به DynamicComponent هدایت میشوند که توسط DynamicRouteService فیلتر شدهاند و این سرویس CanActivate را پیادهسازی میکند. در DynamicRouteService، ما next.params را نگاشت میکنیم تا یک Observable<boolean> به ماژول Router بازگردانیم و مسیر یابی تا زمان تکمیل این observable نگه داشته خواهد شد.
در app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: "prefix" },
{ path: 'login', component: LoginComponent },
{ path: 'home', component: DashboardComponent },
{ path: 'settings', component: SettingsComponent },
{ path: '404', component: PageNotFoundComponent },
{ path: ':id', component: DynamicComponent, canActivate: [DynamicRouteService] },
];
توجه: مطمئن شوید که DynamicRouteService را به providers در app.module.ts اضافه کنید
در dynamic-route.service.ts
export class DynamicRouteService implements CanActivate {
constructor(private router: Router) { }
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return next.params.pipe(first()).pipe(map(param) => {
console.log(param.id)
return ... // make any API call with param.id and get a response as promise
.then( (response) => {
... // do whatever you want to do on success
return true;
})
.catch( (error) => {
console.error(error);
this.router.navigate(['404']); // route to 404 on failure
return false;
}))
}
}
}
