Share
Embed
Transcript
110 slides
-
Hi, I’m Nikas Front end dev @ Wix.com/app-market @nikaspran github/nikaspran
nikas.praninskas.com -
The next 45 minutes of your life Prior art What
is the New Angular Router? Migration Current state of affairs -
Deep linking is awesome share information know at a glance
contextualize bookmark The ability to -
Don’t break the back button The back button is free
and obvious It is very easy to be annoying if you break navigation -
Structure for your app Forces you to modularize Makes apps
easy to understand -
ngRoute - very simple .config(function($routeProvider){ $routeProvider .when('/user/:id', { templateUrl: 'user.html',
controller: 'UserCtrl', resolve: {...} }) .when('/user/:id/edit', { templateUrl: 'userEdit.html', controller: 'UserEditCtrl' }); }); One view per app One URL, one view <a href="user/123"></a> <a href="user/123/edit"></a> <div ng-view></div> -
Good things come to those who wait .when('/user/:id', { ...,
resolve: { user: function($q) { var data = $q.defer(); //get user data from API return data.promise; } } }) Inject into controller from configuration Waits until promises resolve -
Simple, but too simple Difficult to do things like modals
sidebars deep routes parallel views complex apps -
UI Router - much more powerful <a ui-sref="layout.user"></a> <a ui-sref="layout.user.edit(
{id: 123})"></a> <div ui-view="sidebar"></div> <div ui-view="content"></div> State based routing .config(function($stateProvider){ $stateProvider .state('layout', { abstract: true, templateUrl: 'layout.html' }) .state('layout.user', { views: { sidebar: { templateUrl: 'sidebar.html', controller: 'SidebarCtrl', resolve: {...} }, content: {...} } }) }); -
States are intuitive States describe architecture Not everything has to
have a URL Easy to link to parts of your application -
Customizability is very important and UI Router does a pretty
good job: christopherthielen/ui-router-extras onEnter, onExit, $stateChange*, ... -
…up to a point Do something complex enough and you
start fighting the router -
Multiple paths that preserve history 1. Open App Market 2.
Click App icon Use direct link to App (eg. via social) wix.com/app-market/:appName/overview -
we spent way too much time fighting our tools at
the time, we did a fork still not obvious how to do it all -
Angular 2 is coming Can we do better with a
clean slate? -
…but have great defaults Make it obvious intuitive quick easy
to learn and use -
State-like reverse routing Automatically deep link to components <a router-link="user"
router-params="{id: 123}"></a> <a href="./user/123"></a> -
State-like reverse routing Link to multiple components at once sidebar
content mainPage <a router-link="mainPage"></a> -
Thought experiment: The router is one of the most important
parts of an application -
If it is designed for Angular 2 but also works
with Angular 1 -
If it is designed for Angular 2 but also works
with Angular 1 We have a migration plan! ng1 ng2 -
ま よ た ね ぃ み む ょ じ を
り ぉ ね ぼ り ぉ け ば や わ ゎ お ら ゆ ろ り ぉ け ば や わ ゎ お ら ゆ じ ゃ で り ぐ き な ゕ ゟ た ょ を こ で ぷ お じ ぁ だ ど ぬ す っ ら び ぅ ぬ だ ざ ま よ た ね ぃ み む ょ じ を り ぉ ね ぼ び ぃ ぅ や こ そ ぶ や あ ゝ ぼ ゆ ぺ み ひ じ え わ ぶ す ぞ ぷ ゑ っ ぞ ほ だ Show me the code already ま よ た ね ぃ み む ょ じ を り ぉ ね ぼ ろ り ぉ け ば や わ ゎ お ら ゆ ほ ぞ ぁ ろ り ぉ け ば や わ ゎ お び ぃ ぅ や こ そ ぶ や あ ゝ ぼ ゆ ぺ み ひ じ え わ ぶ す ぞ ぷ ゑ っ ぞ ほ だ ま よ た ね ぃ み む ょ じ を ろ り ぉ け ば や わ ゎ お ら ゆ ほ び ぃ ぅ や こ そ ぶ や あ ゝ ぼ ゆ ぺ み ひ じ え わ ぶ す ぞ ぷ ゑ っ ま よ た ね ぃ み む ょ じ を り ぉ ね ぼ ろ り ぉ け ば や わ ゎ お ら ゆ ほ ぞ ぁ ろ り ぉ け ば や わ ゎ び ぃ ぅ や こ そ ぶ や あ ゝ ぼ ゆ ぺ み ぶ す ぞ ぷ ゑ っ ぞ ほ だ り ぉ け ば や わ ゎ お ら ゆ ほ ぞ ぁ ろ り ぉ け ば や わ ゎ お ぉ け ば や わ ゎ お ら ゆ ほ ろ り ぉ け ば や わ ゎ お ら ゆ け ば や わ ゎ お ら ゆ ほ ぞ ぁ ろ り ぉ け ば や わ ゎ お ら ゆ を こ で ぷ お じ ぁ だ ど う え を こ で ぷ お じ ぁ だ ど う え ぬ す っ ら び ぅ ぬ だ ざ ぬ す っ ら び ぅ ぬ だ ざ ぬ す っ ら び ぅ ぬ だ ざ ぶ す ぞ ぷ ゑ っ ぞ ほ だ ゝ ぼ ゆ ぺ み あ ゝ ぼ ゆ ぺ み あ ゝ ぼ ゆ ぺ み あ ゝ ぼ ゆ ぺ み ぬ す っ ら び ぅ ぬ す っ ら び ぅ ぬ だ ざ ぬ す っ ら び ぅ ぬ だ ざ ぬ す っ ら び ぅ ぬ だ ざ ぬ す っ ら び ぅ ぬ あ ゝ ぼ ゆ ぺ み ゝ ぼ ゆ ぺ み ま よ た ね び ぃ ぅ や こ そ ぶ ま よ た ね ぃ み -
Angular 2.x import {ListComponent} from './list'; import {EditComponent} from './edit';
@Component({...}) @View({template: '...'}) @RouteConfig([ {path: '/', component: ListComponent}, {path: '/edit/:id', component: EditComponent} ]) class AppComponent() { ... } -
Angular 1.x $componentMapper .setCtrlNameMapping(comp => comp + 'Ctrl'); $componentMapper .setTemplateMapping(comp
=> comp + '.html'); AppController.$routeConfig([ {path: '/', component: 'list'}, {path: '/edit/:id', component: 'edit'} ]); function AppController() { ... } -
Static configuration {path: '/', component: 'list'}, {path: '/edit/:id', component: 'edit'}
{path: '/', component: ListComponent}, {path: '/edit/:id', component: EditComponent} ng2 ng1 === easy to port -
Template - ng1 $componentMapper .setTemplateMapping(function (compName) { return templatePath; });
./components/my-comp/my-comp.html myComp Uses component convention by default: Customize as you want: -
Controller - ng1 $componentMapper .setCtrlNameMapping(function (compName) { return controllerName; });
Adds “Controller” by default: Customize as you want: MyCompController myComp -
Router Each component can have a child router Child routers
manage their sub-URLs -
You can release it as an artifact They can mount
and use it SO,if you build an amazing component with deep linking -
Router - ng2 @RouteConfig([ {path: '/', redirectTo: '/main'}, {path: '/main',
component: MainComp}, {path: '/other', components: {}, as: 'other'} ]) -
Router - ng1 function MainController($router) { $router.config([...]); } function MainController()
{...}; MainController.$routeConfig([...]); -
Outlet - think ui-view <div ng-outlet></div> <div ng-outlet="left"></div> {path: '/',
components: { default: 'posts', left: 'users' }} -
Pipeline - it’s middleware Takes Instruction Transforms it ..with side
effects $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep -
Let’s ask the router to go somewhere $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep
$runCanActivateHookStep $loadTemplatesStep $activateStep router.navigate('users/123') -
Router generates the instruction $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep
router.navigate('users/123') recognize(): instruction matchedUrl component router children: Instruction[] -
Once more unto the pipeline! $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep
$activateStep matchedUrl component router children: Instruction[] -
$setupRoutersStep $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep matchedUrl component router
children: Instruction[] Add child instruction routers -
$initLocalsStep $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep matchedUrl component router
children: Instruction[] locals Add locals: $router, $routeParams -
$runCanDeactivateHookStep $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep matchedUrl component router
children: Instruction[] locals Visit old outlets; run canDeactivate() -
$runCanActivateHookStep $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep matchedUrl component router
children: Instruction[] locals Visit new outlets; run canActivate() -
$loadTemplatesStep $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep matchedUrl component router
children: Instruction[] locals template Load templates, add to instruction -
$activateStep $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep matchedUrl component router
children: Instruction[] locals template Recursively activate outlets (ctrl, tmpl, ...) -
Create our step factory .factory('resolveStep', function ($q, $injector) { return
function resolve(instruction) { }; }) -
Recurse over instructions .factory('resolveStep', function ($q, $injector) { return function
resolve(instruction) { var promises = []; return instruction.router.traverseInstruction( instruction, queueResolves ).then(function () { return $q.all(promises); }); }; }) -
Get config from instruction .factory('resolveStep', function ($q, $injector) { return
function resolve(instruction) { var promises = []; function queueResolves(instruction) { var cfg = getResolveConfigFrom(instruction); } return instruction.router.traverseInstruction( instruction, queueResolves ).then(function () { return $q.all(promises); }); }; }) -
Queue the resolution .factory('resolveStep', function ($q, $injector) { return function
resolve(instruction) { var promises = []; function queueResolves(instruction) { var cfg = getResolveConfigFrom(instruction); var resolve = $q.when($injector.invoke(cfg.value)); promises.push(resolve.then(function (resolved) { })); } return instruction.router.traverseInstruction( instruction, queueResolves ).then(function () { return $q.all(promises); }); }; }) -
Add it to locals .factory('resolveStep', function ($q, $injector) { return
function resolve(instruction) { var promises = []; function queueResolves(instruction) { var cfg = getResolveConfigFrom(instruction); var resolve = $q.when($injector.invoke(cfg.value)); promises.push(resolve.then(function (resolved) { instruction.locals[cfg.key] = resolved; })); } return instruction.router.traverseInstruction( instruction, queueResolves ).then(function () { return $q.all(promises); }); }; }) -
Insert it into the pipeline .config(function ($pipelineProvider) { var steps
= $pipelineProvider.steps; var atPosition = steps.indexOf('$runCanDeactivateHookStep) + 1; //insert resolveStep after $runCanDeactivateHookStep steps.splice(atPosition, 0, 'resolveStep'); $pipelineProvider.config(steps); }) -
End result $setupRoutersStep $initLocalsStep $runCanDeactivateHookStep $runCanActivateHookStep $loadTemplatesStep $activateStep resolveStep Part
of the pipeline Called on every transition -
Dynamic loading Add new routes @ runtime: function SomeController($router) {
$router.config([...]); } -
ng1 + ngComponentRouter ng2 ng 1.5 + new router Some
things we know -
And some we don’t ng1 + X ng1 + ngComponentRouter
ng2 ng 1.5 + new router ? -
Migration strategies from UI Router are a TODO item But
one can dream… -
Multi-components are sort of like states $router.config([{ as: 'category', path:
'/:category', components: { sidebar: 'Sidebar', content: 'Category' } }]); $stateProvider .state('category', { url: '/:category', views: { sidebar: { templateUrl: '...', controller: '...' }, content: { templateUrl: '...', controller: '...' } } }); ui-router compRouter -
Directives are sort of similar <a router-link="user" router-params="{id: 123}"></a> <a
ui-sref="user({id: 123})"></a> ~== ui-router compRouter -
Pipeline! e.g. we saw how to add resolve we can
fake $stateChangeEvents someone write an adapter? :) -
Write unit tests UI Router configuration is testable Write now,
then verify migration later http://nikas.praninskas.com/test-ui-router Component Router should be even easier -
Current $state of affairs //TODO: draw something totally awesome like
robots and lasers and stuff and maybe a cat -
Codebase is a bit of a mess: 1.x has not
worked for over a month everything in ng2 repo until build is stable still working out core issues (i.e. reverse routing) -
In place of a summary Designed from the ground up;
New ideas for the future; Lessons learned from the past; Not yet ready.