I have a component that is decorated with #CanActivate.
#Component({
// ...
})
#CanActivate(() => false)
export class UserManagementComponent {
// ...
}
In my navigation menu I'd like to disable or hide the link that navigates to this route. How would I go about doing this?
<a [routerLink]="['UserManagement']">User management</a>
PS: I'm still on the deprecated routing mechanism, not the rc1 version.
If you move the calculation of the #CanActivate(() => ...) return value to a service, then you can access it from your whole application. You can create a directive and add it to the routerLink that injects the service and disables the routerLink when the condition for the route returns false.
See http://almerosteyn.com/2016/04/linkup-custom-control-to-ngcontrol-ngmodel for how to use DI in #CanActivate.
See Angular 2, disable routerLink and Angular2, what is the correct way to disable an anchor element? for how to disable a routerLink
I believe having an injected service is a way to go, because if you ever need to route to your component from other additional components, it'll be much easier and you don't have to duplicate the reflection code.
That being said, I was curious on how to do the reflection indeed. So it appears that's supposed to be added to ES7 specification. For now, 'reflection-metadata' package pollyfills for it and that's what Angular2 uses internally too. Although 'Reflect' is already part of TypeScript library, I didn't find any meta-data reflection and seems it only support object reflections.
//This is a bit hacky, but it might help.
...
export class Test {
...
logType(target : any, key : string) {
//stop TypeScript from mapping this to TypeScript Reflect.
var Reflect = Reflect || {};
//You still get an error if you havn't added the Reflect.js script in the page
var t = Reflect.getMetadata("design:type", target, key);
console.log('key:' + key + ' type: ' + t.name);
}
...
}
Sources:
http://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-4
https://www.npmjs.com/package/reflect-metadata
Related
While working with React, i would like to display component name in an attribute of the component. E.g. if I have a component <LoginBox /> I would like it to be rendered as
<div data-react-name="LoginBox">...</div>
But I want this to be done automatically for each transpiled component. Reason for this is automated testing when I'd check for rendered elements in HTML/DOM, currently a component is not differentiated by the name in rendered HTML.
I thought I'd write a babel plugin, but I have no idea what visitors I'd use and how to make it robust enough. I tried google for such a plugin but I have no idea how it would be called and found nothing useful.
So is there any plugin or any way to achieve this?
Thanks
Now after a year, as I'm rethinking, it should be quite easy.
For more details on writing plugins see handbook.
Use ASTexplorer to inspect what AST would your code result in. And then, for generated tree, prepare visitors. So e.g. with code:
<div><Custom some-prop="prop">Some text</Custom></div>
we would infer, that we need to use visitor JSXOpeningElement and alter node's property attribute. To this property - array we would add a new element that we would create by Babel.types.jsxAttribute(name, value). We will get the name of tag from node's property .name.name (the name string is nested inside name object). We also need to use appropriate types. So it would look like this:
module.exports = function(Babel) {
return {
visitor: {
JSXOpeningElement(path) {
const name = Babel.types.jsxIdentifier('data-testname');
const value = Babel.types.stringLiteral(path.node.name.name);
path.node.attributes.push(Babel.types.jsxAttribute(name, value));
}
}
};
};
The code is tested with the ASTExplorer.
I'm discovering EmberJS and started to migrate an existing website to this framework. I was having an issue with a Bootstrap-based dropdown. This issue actually helped me understand Ember's concepts a bit better but I still have some questions.
I used the ember-bootstrap module to generate this dropdown (among other things) and here is what the code is supposed to be:
{{#bs-dropdown as |dd|}}
{{#dd.button}}
Sort by
{{/dd.button}}
{{#dd.menu as |ddm|}}
{{#ddm.item}}{{#ddm.link-to "index"}}Price low to high{{/ddm.link-to}}{{/ddm.item}}
{{#ddm.item}}{{#ddm.link-to "index"}}Price high to low{{/ddm.link-to}}{{/ddm.item}}
{{/dd.menu}}
{{/bs-dropdown}}
Now, I want some javascript code to be executed when the user clicks on one of the items. After checking the module's documentation, I found where the menu item component was defined and edited its code as follows:
export default Component.extend({
layout,
classNameBindings: ['containerClass'],
/* ... */
actions: {
// My addition
sortByPrice(param){
alert("sorting");
},
// End of the addition
toggleDropdown() {
if (this.get('isOpen')) {
this.send('closeDropdown');
} else {
this.send('openDropdown');
}
},
},
});
Then I updated the hbs file as follows:
{{#dd.menu as |ddm|}}
{{#ddm.item action "sortByPrice" low_to_high}}
{{#ddm.link-to "index" action "sortByPrice" low_to_high}}
Prix croissant
{{/ddm.link-to}}
{{/ddm.item}}
{{/dd.menu}}
This didn't work, and that's why you I added the *action* to the link-to element as well and declared similarly the action on its component file.
import LinkComponent from '#ember/routing/link-component';
export default LinkComponent.extend({
actions: {
sortByPrice(param){
alert("sorting");
console.log("sorting");
},
},
});
As you can see, the *link-to* component extends the LinkComponent one. I eventually understood that it wasn't possible for this element to handle click events natively, as explained in this thread.
Out of frustration, I ended up with a less elegant approach that still does the trick:
{{#bs-dropdown id="sort" as |dd|}}
{{#dd.button}}
Sort by
{{/dd.button}}
{{#dd.menu as |ddm|}}
{{#ddm.item action "sortByPrice" low_to_high}}
<a
class="dropdown-item"
onclick="sortByPrice('low_to_high'); return false;"
href="#"
>
Price low to high
</a>
{{/ddm.item}}
{{/dd.menu}}
{{/bs-dropdown}}
Now here are my questions:
Why is it that defining actions on both the Component file and the hbs one didn't change the result?
Why doesn't the LinkComponent handle click events natively? I get that a link is supposed to redirect users to a new page (which is still arguable), but the DOM event is still fired, so does Ember deliberately ignore it and choose not to let developers handle it? I want to know the logic behind this.
Is there a better approach than my solution?
Thanks.
Cheers for studying EmberJS and posting a beautiful, explicit question!
Your mistakes
Never modify the code inside node_modules/ and bower_components/ folders. If you really need to monkey-patch something, you can do it in an initializer. But your use case does not require monkey patching.
You attempted to define an action in the menu item component, but you apply it in a parent template. That action has to be defined in that parent's template component/controller.
This invocation is incorrect:
{{#ddm.link-to "index" action "sortByPrice" low_to_high}}
Here are the problems:
The ddm.link-to component is supposed to create a link to another route. It does not seem to support passing an action into it.
You're just passing a bunch of positional params to the component. If ddm.link-to did support accepting an action, the correct invocation would look like this:
{{#ddm.link-to "index" argName=(action "sortByPrice" low_to_high)}}
In this case, "index" is a position param and argName is a named param.
low_to_high without quotes is a reference to a property defined on the current scope. You probably meant a string instead: "low_to_high".
Never use JS code in template directly. This you should never do in Ember:
<a onclick="sortByPrice('low_to_high'); return false;">
Instead, pass an action (defined in the local scope: in a component or controller):
<a onclick={{action 'sortByPrice' 'low_to_high'}}>
The onclick property name is optional. An action defined without a property implies onclick (you only need to provide the property name if you need to attach the action to a different event):
<a {{action 'sortByPrice' 'low_to_high'}}>
For the link to be styled properly in a browser, a href attribute is required. But you don't have to pass a value '#' to it. The hash symbol was required in old-school apps to prevent the link from overwriting the URL. Ember overrides URL overwriting for you, so you can simply pass an empty href.
Here's the final correct usage:
<a href {{action 'sortByPrice' 'low_to_high'}}>
Answers to your questions
Why is it that defining actions on both the Component file and the hbs one didn't change the result?
Because you defined them in different scopes.
If you define an action in app/components/foo-bar.js, the action must be applied in app/templates/components/foo-bar.hbs.
If you define an action in app/controllers/index.js, the action must be applied in app/templates/index.hbs.
Why doesn't the LinkComponent handle click events natively? I get that a link is supposed to redirect users to a new page (which is still arguable), but the DOM event is still fired, so does Ember deliberately ignore it and choose not to let developers handle it? I want to know the logic behind this.
In a PWA, you do not do actual page redirects. Such a redirect would reload the whole app.
Instead, the LinkComponent overrides the click and tell the Ember's routing system to perform a transition. Routes must be set up properly and the route passed to the LinkComponent must exist.
It seems that your goal is not to perform a transition but to change a variable, so the LinkComponent is not applicable here. That's unless you wire the sort order property to an URL query param, in which case you can change the sort order by making a transition to a different query param.
Is there a better approach than my solution?
See below for the simplest approach that uses ember-bootstrap's dropdown.
A working example
Controller:
export default Ember.Controller.extend({
isSortAccending: true,
actions: {
changeSortDirection (isSortAccending) {
this.set('isSortAccending', isSortAccending);
}
}
});
Template:
<p>
Current sort order:
{{if isSortAccending "ascending" "descending"}}
</p>
{{#bs-dropdown as |dd|}}
{{#dd.button}}
Sort by
{{/dd.button}}
{{#dd.menu as |ddm|}}
{{#ddm.item}}
<a href {{action "changeSortDirection" true}}>
Price high to low
</a>
{{/ddm.item}}
{{#ddm.item}}
<a href {{action "changeSortDirection" false}}>
Price high to low
</a>
{{/ddm.item}}
{{/dd.menu}}
{{/bs-dropdown}}
Here's a working demo.
I just started using Sapper (https://sapper.svelte.technology) for the first time. I really like it so far. One of the things I need it to do is show a list of the components available in my application and show information about them. Ideally have a way to change the way the component looks based on dynamic bindings on the page.
I have a few questions about using the framework.
First, I'll provide a snippet of my code, and then a screenshot:
[slug].html
-----------
<:Head>
<title>{{info.title}}</title>
</:Head>
<Layout page="{{slug}}">
<h1>{{info.title}}</h1>
<div class="content">
<TopBar :organization_name />
<br>
<h3>Attributes</h3>
{{#each Object.keys(info.attributes) as attribute}}
<p>{{info.attributes[attribute].description}} <input type="text" on:keyup="updateComponent(this.value)" value="Org Name" /></p>
{{/each}}
</div>
</Layout>
<script>
import Layout from '../_components/components/Layout.html';
import TopBar from '../../_components/header/TopBar.html';
let COMPONENTS = require('../_config/components.json');
export default {
components: {
Layout, TopBar
},
methods: {
updateComponent(value) {
this.set({organization_name: value});
}
},
data() {
return {
organization_name: 'Org Name'
}
},
preload({ params, query }) {
params['info'] = COMPONENTS.components[params.slug];
return params;
}
};
</script>
Now my questions:
I notice I can't #each through my object. I have to loop through its keys. Would be nice if I could do something like this:
{{#each info.attributes as attribute }}
{{attribute.description}}
{{/each}}
Before Sapper, I would use Angular-translate module that could do translations on strings based on a given JSON file. Does anyone know if a Sapper/Svelte equivalent exists, or is that something I might need to come up with on my own?
I'm not used to doing imports. I'm more use to dependency injection in Angular which looks a bit cleaner (no paths). Is there some way I can create a COMPONENTS constant that could be used throughout my files, or will I need to import a JSON file in every occurence that I need access to its data?
As a follow-up to #3, I wonder if there is a way to better include files instead of having to rely on using ../.. to navigate through my folder structure? If I were to change the path of one of my files, my Terminal will complain and give errors which is nice, but still, I wonder if there is a better way to import my files.
I know there has got to be a better way to implement what I implemented in my example. Basically, you see an input box beside an attribute, and if I make changes there, I am calling an updateComponent function which then does a this.set() in the current scope to override the binding. This works, but I was wondering if there was some way to avoid the function. I figured it's possible that you can bind the value of the input and have it automatically update my <TopBar> component binding... maybe?
The preload method gives me access to params. What I want to know if there is some way for me to get access to params.slug without the preload function.
What would be really cool is to have some expert rewrite what I've done in the best possible way, possibly addressing some of my questions.
Svelte will only iterate over array-like objects, because it's not possible to guarantee consistent behaviour with objects — it throws up various edge cases that are best solved at an app level. You can do this sort of thing, just using standard JavaScript idioms:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} ...</p>
{{/each}}
<!-- or, if you need the key as well -->
{{#each Object.entries(info.attributes) as [key, value]}}
<p>{{attr.description}} ...</p>
{{/each}}
Not aware of a direct angular-translate equivalent, but a straightforward i18n solution is to fetch some JSON in preload:
preload({ params, query }) {
return fetch(`/i18n/${locale}.json`)
.then(r => r.json())
.then(dict => {
return { dict };
});
}
Then, you can reference things like {{dict["hello"]}} in your template. A more sophisticated solution would only load the strings necessary for the current page, and would cache everything etc, but the basic idea is the same.
I guess you could do this:
// app/client.js (assuming Sapper >= 0.7)
import COMPONENTS from './config/components.json';
window.COMPONENTS = COMPONENTS;
// app/server.js
import COMPONENTS from './config/components.json';
global.COMPONENTS = COMPONENTS;
Importing isn't that bad though! It's good for a module's dependencies to be explicit.
You can use the resolve.modules field in your webpack configs: https://webpack.js.org/configuration/resolve/#resolve-modules
This would be a good place to use two-way binding:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} <input bind:value=organization_name /></p>
{{/each}}
Yep, the params object is always available in your pages (not nested components, unless you pass the prop down, but all your top-level components like routes/whatever/[slug].html) — so you can reference it in templates as {{params.slug}}, or inside lifecycle hooks and methods as this.get('params').slug, whether or not a given component uses preload.
If I have a template in a component that references non-existant components, angular 1.6 seems perfectly happy to render it as nothing at all. For example I have a route currently that looks like:
when('/something',{
title: 'Something',
template: "<mycomponent></mycomponent>",
})
If I forget to register mycomponent on my application, this route renders nothing. Is there some mode I can use in angular that will cause a harder error in a case like that? Or at least print something to the console when it occurs?
To be perfectly clear, I have this issue with both top level components referenced by the router, as well as other child components that those reference recursively.
No, there is no option for that. By the way "non rendered" components are a benefit IMO, because you could override this slot later.
A short example:
when('/something',{
title: 'Something',
template: "<slot><mycomponent></mycomponent></slot>",
})
assume you want to override the ui-part of mycomponent, just define a component for "slot"
There was a routeProvider.otherwise before. Not sure if it’s still supported. I’m on a phone so limited. Let me know how it goes.
UI Router supports components for routes, this means that it is capable of triggering an error if a component doesn't exist.
AngularJS currently doesn't offer component router. This means that route template is compiled as any other template with $compile, and unknown selectors are just ignored.
It is possible to patch ngRoute ngView directive to additionally check if a route that is expected to route to a component actually has it compiled.
// the whole list can be retrieved from commonly available sources
var htmlTags = ['p', 'div', ...];
app.directive('ngView', function () {
return function($scope, $element, $attrs) {
if (!DEBUG_MODE)
return;
if ($element.children().length !== 1)
return;
var routeComponent = angular.element($element.children()[0]);
var routeComponentTagName = routeComponent.prop('tagName').toLowerCase();
if (htmlTags.indexOf(routeComponentTagName) >= 0)
return;
var routeComponentName = $attrs.$normalize(routeComponentTagName);
if (!routeComponent.controller(routeComponentName)) {
console.warn(routeComponentTagName + ' element is non-standard yet not a component');
}
}
});
AngularJS already has $normalize() which is primarily intended for attributes and strips some specific prefixes, but as long as the components names don't have x or data prefix, it can work as generic kebab to camel case transformer.
There may be other ways to detect if a component was compiled on child element. The code above will trigger false negative if there already is parent routeComponentName component, because controller() travels up the hierarchy.
And the proper way to handle this problem is to provide test coverage for the application. Router-related cases should likely be handled in integration or e2e tests.
If i eg. use a select drop down input field in my header (more precisely in my sitewide nav-bar which is custom element), and have that value set globally in a shared state object. If I - on languageChanged(value) (inside the nav-bar custom element) also change the this.i18n.setLocale('de-DE')
How would I then refresh the i18n translated string interpolated values (eg. ${'status_deceased' | t} ) inside my templates without having to navigate to a new route and back as I do right now?
I found this issue on github about the problem https://github.com/aurelia/i18n/issues/6 but since I need this to work, I'm hoping that some clever workaround exists, except from having to use window.location to reload the page :/
Edit
If I'm understanding this correctly it seems it might just be my lucky day, and such a feature has just been added 8 days ago, although still undocumented: https://github.com/aurelia/templating-resources/pull/126 - Can anyone figure out and tell me how to implement this, using this new feature perhaps? If I figure this out myself, I'll update this thread with the solution :-)
When the next release goes out you'll be able to assign a "signal name" to a binding using the signal binding behavior like this:
<h1>${'title_key' | t & signal:'i18n'}</h1>
<p>${'content_key' | t & signal:'i18n'}</p>
The & symbol denotes a "Binding Behavior" (as opposed to | for value-converters). Binding behaviors are resources that add "behavior" to a binding. They have full access to the binding instance and are notified prior to the binding's bind and unbind lifecycle events.
Aurelia will ship with several built-in binding behaviors: "throttle", "debounce", "one-time", "signal" etc. You also have the option of creating your own binding behaviors.
In the example above we've given the title and content interpolation bindings a "signal" name of "i18n". The name is arbitrary, we just need to know what it is so we can "signal" the bindings to refresh using the BindingSignaler like this:
import {BindingSignaler} from 'aurelia-templating-resources';
import {inject} from 'aurelia-framework';
#inject(BindingSignaler)
export class App {
constructor(signaler) {
this.signaler = signaler;
}
// invoking this method will refresh all bindings in the application
// with the "signal name" of "i18n"
refreshBindings() {
this.signaler.signal('i18n');
}
}
I imagine once the binding behavior feature drops there will be additional work in the i18n plugin to combine the t value converter with some version of the signal binding behavior to enable terse binding expressions that take care of both translation and refreshing the bindings when the language changes so you might want to sit tight for the time being.
EDIT
If you need something today you could take advantage of an existing Aurelia feature: bindings are re-evaluated when converter parameters change.
Create a new class:
export class LanguageChangedNotifier {
signal = 0;
notify() {
this.signal++;
}
}
inject this class into all view-models and add the instance as a property:
#inject(LanguageChangedNotifier)
export class App {
constructor(notifier) {
this.notifier = notifier;
}
}
Use the notifier in your t bindings (it won't impact the behavior of the t value converter):
${'status_deceased' | t:notifier.signal}
When you change the locale, use the notifier to refresh the bindings:
this.notifier.notify();