Angular sub-navigation based on route - html

I'm building an Angular application with two levels of navigation in the header.
The top-level navigation are visible on all routes in the application, however the second-level navigation is context-specific and only exists on certain routes.
For example, when viewing a specific course in a university webapp, the second-level navigation might have links to description, prerequisites, the different subjects, etc.
One way of doing it is creating a subnav component that has a switch statement and renders the correct subnav component based on the route:
<div [ngSwitch]="currentRoute">
<course-sub-nav *ngSwitchCase="course"></course-sub-nav>
<students-sub-nav *ngSwitchCase="students">...</students-sub-nav>
<support-sub-nav *ngSwitchCase="support">...</support-sub-nave>
</div>
I don't like that solution because now the sub-nav component knows too much about all the different context-specific sub-navs.
Another approach is to use a service to manage it. i.e. having a SubNavService.setNavigationLinks(links: NavLink[]) that takes an array of links. The sub-nav component can then listen to changes to the SubNavService and dynamically render a list of sublinks.
This solution is a bit cleaner however it does mean that I need to call that service on ever top-level component to ensure that the sub-nav is being updated. I'd also need to clear the links on pages that don't require sub-navs.
A third solution might be doing something like:
<!-- In header.component.html -->
<div class="sub-nav">
<ng-content="subnav"></ng-content>
</div>
<!-- IN course-page.component.html -->
<div>
<subnav>
<!-- my nav links -->
</subnav>
<div class="course-content">
<router-outlet></router-outlet>
</div>
</div>
I like this approach the most given that if a subnav component exists, the subnav will be rendered, and if not, nothing will show. Additionally, the relevant component has full control over rendering the links within the subnav. however given that course-page isn't actually a child of the header, I'm not sure how I can make it work.
I was wondering if there's any way to get the last approach to work. If not, would the second approach be the most pragmatic solution to this problem or is there a cleaner solution that exists?

The cleanest solution would be the second you mentioned.
Having a service that would handle the menu - menu component would subscribe and listen for changes, meanwhile other components would handle what items they need to have in the menu.

Related

Correct way to scroll to dynamic angular page

I am searching for the correct way to correct to scroll in a page that will load dynamic information. These informations are asynchronous so to avoid my user seeing the whole page constructing itself I have a boolean flag like so :
<div *ngIf="loaded"> ... </div>
My problem is that I want to scroll to an anchor using the angular router, but that anchor doesn't exist yet at this moment because the load isn't finished.
The anchor :
<hr id="my_anchor">
the code I use to load the page and get to that anchor :
<a [routerLink]="['/some/route/', idParameter]" fragment="my_anchor">...</a>
You probably want to use virtual scrolling, available through the Angular CDK:
https://material.angular.io/cdk/scrolling/examples
https://medium.com/codetobe/learn-how-to-us-virtual-scrolling-in-angular-7-51158dcacbd4
This approach dynamically loads only those components that are on screen, allowing you to load extremely large datasets.
It is also possible to code this manually using:
const el: any = document.elementFromPoint(x, y);
This Javascript function determines which HTMLElement is located at a specific x, y, coordinate and thereby determining which elements are on screen. Using this information you can wrap all items in a list like so:
<ng-container *ngFor="let data of datas">
<ng-container *ngIf="data.isOnScreen">
<app-my-component></app-my-component>
</ng-container>
<ng-container *ngIf="!data.isOnScreen">
<div class="empty-div"> </div>
</ng-container>
</ng-container>
and style the empty-div to be the same size as a non empty div. This ensures that scrolling works. I got this working well. However, no doubt the CDK makes this a whole world smoother and easier.
The only benefit with my custom approach is it gives you total control. You can easily use:
list.scrollTop = 999;
to scroll to any position in the list, thus supporting anchors (where list is the HTML list element that will scroll). Not for the feint hearted though, would only recommend this for confident coders.

Using Voiceover to jump to internal links

I am looking for a way to go from my navigational links using voice-over accessibility to jump to the internal part of the page that link I connected to.
For example:
I have a list of links on my left-hand side. One is for Forms. When I click on "Forms" my list of forms will populate to the right of that panel, and then if you click on a specific form, that Form will appear to the right of that panel. The page contains 3 panels with Navigation on the left, list in the middle, Form on the right.
Right now if I click on the Form link, I have to tab through my entire nav panel to get to the newly opened Forms list.
Are there some ARIA elements I am missing that will help tab directly into my Forms List from the Forms link?
There are two simple ways to do this.
Note that neither of the following ways need any aria to work, the aria in the following examples is purely for best practices when adding sections and headings to a page.
Option 1 - anchors
The first is using anchors pointing to ids on the page.
in your side bar
forms
main page section
<section aria-labelledby="forms">
<h2 id="forms">Forms</h2>
<!---your forms --->
</section>
Notice how I gave the page content a heading (pick an appropriate heading level) and then labelled the section with aria-labelledby. None of this is required to make this method work but it is a good practice.
The only thing you need to do is to make sure your href matches the ID of the heading.
However given that you are populating the forms on the right side of the page (I am assuming with an AJAX call) you may want to manually manage focus with JavaScript....
Option 2 - Use JavaScript and .focus()
If you are using JavaScript it is a similar principle, give the section heading an ID, but this time once the forms list has loaded set focus on the list heading.
html
<!--your link in the menu -->
forms
<!--section on the page, I omitted the aria here for clarity / simplicity but it is still needed-->
<section>
<h2 id="forms" tabindex="-1">Forms</h2>
</section>
JavaScript
// start: however you have this implemented at the moment
const formsLink = document.querySelector("#getForms");
formsLink.addEventListener('click', function(e){
e.preventDefault();
get('yourURL').then((data) => {
// populate the list
list.update(data);
// end: however you have this implemented at the moment
// once the list is populated set focus on the heading (last thing to do after everything else is done)
document.querySelector('#forms').focus();
});
});
Notice how in this example the <h2> has a tabindex="-1".
We need this to allow programmatic focus. We use -1 so we can focus the heading via JavaScript but it does not get added to the page focus order (so you can't access it with Tab).

Generate different pages on the basis of different contents

Let me assume that I have the following architecture
_components (folders)
x1.html
x2.html
x3.html
I have a first page where I got the information from the YAM section for every component.
At this point I would like to add a link for every component to another page where I will display the component in a bigger manner.
So, let me assume I have, in the first page :
<div class="col-md-1">
<span id= "logos" class="material-icons"></span>
</div>
and In the componentbig.html I would like to open the right component on the basis of the link.
Do you have any suggestions for me ?
If I understand You correctly, then collections might be the feature You are looking for. For example, I'm usually using collections to generate a list of products and then have a specific page for each product also. Also make sure You check the easy tutorial by Ben Balter.

dynamically pass in template to polymer-element

I have been trying create a polymer-element that generates a ul list based on an ajax request and then renders the "li" elements based on templates that can somehow be passsed in.
It's basically an attempt to make a polymer rebuild of the 'select2' library for autocompletion.
So, the base template I have so far looks like this:
<polymer-element name="auto-complete" attributes="url_base item_template">
<aj-ax id="xhr" url="{{url_base}}" params="{}" handle_as="json" on-ajax-response="{{handle_res}}" on-ajax-error="{{handle_err}}"></aj-ax>
<input id="eingabe" type="text" on-keyup="{{process_request}}" on-blur="{{hide_dropdown}}"/>
<div id="dropdown" hidden?="{{hide}}">
<ul>
<template repeat="{{i in items}}">
<li> i.text
<!--
the process_request handler makes the ajax request and sets
the "items" and un-hides the dropdown.
the above works, but I want to make it more generic so that
you can pass in a template that reads the item model such as
<template ref="{{item_template}}" bind></template>
where item_template is the ID of a template in some outside
scope
-->
</li>
</template>
</ul>
</polymer-element >
</div>
I've also tried to make a base auto-complete.html polymer-element and then extend it based on the auto-complete type...but to no avail.
Any thoughts, ideas?
I want to stick to declarative methods if possible and avoid having to build the DOM elements myself with document.createElement
Is that even possible?
thanks!
I've come up with a cool approach to do this actually!
http://jsbin.com/hesejipeha/2/edit
The main idea is that you conditionally render the your template once you've injected any child templates into the shadow DOM (and thus made it possible to call them by ref in scope!)

What is the best HTML approach when form inputs are spread throughout the page?

I am building a faceted search system that has inputs in a sidebar (the facets are check boxes), and an input in the header of the page (the main query box). All of these inputs are submitted simultaneously when the user submits a search.
The only way I can think of to make this work is to wrap the entire page in an HTML form tag. Something like the following pseudo-html:
<form>
<div id='header'>
<logo/>
<input id='q'/>
<!-- a bunch more stuff -->
</div>
<div id='sidebar'>
<div id='sidebar-facets-subsection'>
<input id='facet1'/>
<input id='facet2'/>
<input id='facet3'/>
<!-- a bunch more stuff -->
</div>
<div id='sidebar-form-subsection'>
<form id='unrelated-form'>
<input id='unrelated-input-1'/>
<input id='unrelated-input-2'/>
</form>
</div>
</div>
<!-- a bunch more stuff -->
</form>
This would work, except for three things:
I need to use other forms in the page, as I've indicated above.
I use different django templates to generate the header and the sidebar, making the templates have dependencies on each other.
It's a real mess since the sidebar is in reality about 100 lines, not three.
Is there a more clever way of doing this that I'm not aware of, or is creating huge HTML forms the norm? In circumstances like this, is it better to use Javascript to somehow generate the input entries in a more normal form? Or is that the only option?
Any creative solutions or ideas?
You can make it work with Javascript without sacrifying accesibility
Put all the checkboxes in the header and wrap them in div
Set up and empty but clean side bar
Using Javascript, move you checkboxes from the header into the side bar
Attach a callback to the form.submit event, and when the user submit the form, cancel the event then, take the data from the search field and the checkboxes and send it as an Ajax POST request.
Using a framework like jQuery, it's a 15 minutes job.
If the user has JS enable, the form will post the request and everything will work. If the user doesn't have javascript enable, the checkboxes will be in the header and so they will work, at just the price of a slightly less elegant design.
But people with Javascript disable are used to design changes so it's ok.
Use javascript to populate a hidden field with a list of this checkboxes name=value pairs on form submit and treat this in serverside code, spliting the string into an array, etc.
Please note that this is not a good aprouch, since you loose accecibility to those with javascript disabled. The form tag is the only accessible way of doing so.
You can try to change the layout, if you can, swaping the checkboxes with links of buttons that filters the data, almost the way most ecommerce sites do out there.
I believe you have two options:
1.) a page wide form element. All "submit" buttons submit to the same form and the server-side script processes the form for all filled elements. By page wide, I'm not being literal... The related inputs all in the same form tag. Other forms are placed in other form tags.
2.) multiple forms, with a client side script which populates hidden form fields with the data from the other form before submission.
1 requires more work, but 2 may not work for every visitor.
Do consider the fact that, just because you have one form container, you don't have to necessarily display everything together for the user. Encapsulate inputs in divs and position them according to your will. It may not be easy, but it's definitely possible.