I have a use case with Aurelia where I would like to have a handler run for every view that is attached. (It's an HTML5 polyfill for date and number inputs that would work via querySelector.) I realize that I could call this within every view that I create, but I'm wondering if there's a best practice to set this at a global level. (Note: This could probably be done with a router pipeline step, but all views may not be subject to that, such as views loaded via compose.)
I realize that this could potentially be dangerous, but is there a best practice to add global attached() and detached() handlers for views and viewmodels?
Edit: Looking here (https://github.com/aurelia/templating/blob/ee5b9d6742fddf3d163aee8face6e6a58ba1554c/src/view.js#L259) it looks as though it would be possible to add a hook for a global handler here that took a view as an argument, but I'd rather not have to change the framework code if possible.
My idea would be to create a base viewmodel class with an attached logic, which would contain globally required functionality.
Extended viewmodels could call super.attached() to execute global logic as needed.
You can find a demo here: https://gist.run/?id=fea4069d8a4361c4802c7c5d42105145
This can work with compose as well. I know, it isn't a completely automated solution but an opt-in method, so it would require a bit of additional work on all viewmodels.
Base class - used by all viewmodels
import { inject } from 'aurelia-framework';
#inject(Element)
export class BaseView {
constructor(element) {
this.element = element;
}
attached() {
// global logic goes here
}
}
Example viewmodel - actual implementation
import { BaseView } from './base-view';
import { inject } from 'aurelia-framework';
#inject(Element)
export class ExtendedView extends BaseView {
constructor(element) {
super(element);
}
attached() {
super.attached(); // global logic runs
}
}
Related
In our project we use the linting-config from AirBnB. The is a rule, that says class methods must use this or be declared as static. In theory this rule makes a lot of sense to me, but in the angular context seems to cause some problems. Imagine a component like this (Stackblitz):
import { Component, VERSION } from '#angular/core';
#Component({
selector: 'my-app',
template: '<p>{{doSomething("hello stack overflow")}}'
})
export class AppComponent {
doSomething(string: string): string {
return string.toLocaleUpperCase();
}
}
Now, the linter would complain about doSomething not using this. Wen can now make the function static to satisfy it - but than we would not be able to use the function in the template.
One conclusion would be, that doSomething should not be part of AppComponent but another service for example. But than we would have to wrap the static function in non-static one again. In the end the wrapping function is not much smaller than the original one, so the whole outsourcing to service thing seems kind of pointless. Especially since we speak of functions which are explicitly only useful for the template of the component. It seems to be problematic especially with tracking function for trackBy of ngForOf - they tend to not use a this keyword by nature and are only used in template, so they can not be static.
See Call static function from angular2 template
So is there a meaningful pattern how to handle functions which are used in templates together with this rule or is it just not not a useful rule for angular?
you can also define in a .ts externals functions like:
export function myFunction(name){
return "Hello "+name;
}
You only need in one component
import {myFunction} from './myfile.ts'
Then you can use in .ts
myFunction("Me");
If you want to use in the html you need declare in your .ts
myFunctionI=myFunction;
And use
{{myFunctionI('me')}}
Other option: your .ts like
export function Util() {
return new UtilClass()
}
class UtilClass {
greet(name){
return "Hello "+name;
}
}
And you can
import {Util} from './myfile-util.ts'
console.log(Util.greet("me"))
I found a satisfying solution myself:
I convert the those function - small, UI-related, used (only) in template, not using the scope (this) as fields, holding arrow functions.
doSomething = (string: string): string => string.toLocaleUpperCase();
For your case, I think a pipe is better.
I have a constants file constants.ts:
export const C0NST = "constant";
I access it in a service some.service.ts like so:
import { C0NST } from './constants';
console.log(C0NST); // "constant"
However, when I access it in a component template:
some.component.ts:
import { C0NST } from './constants';
some.component.html:
{{ C0NST }} <!-- Outputs nothing -->
However defining a member in the component class works:
some.component.ts
public const constant = C0NST;
some.component.html
{{ constant }} <!-- constant -->
I don't understand why I was able to access the imported constant directly in the service class but not in the component template even though I imported it in the component class.
In Angular2, the template can only access fields and methods of the component class. Everything else is off-limits. This includes things which are visible to the component class.
The way to go around this is to have a field inside the component, which just references the constant, and use that instead.
It's one limitation of the design, but perhaps you should think a bit more about why you need a constant in the template in the first place. Usually these things are used by components themselves, or services, but not the template.
Since in the Component's template you can only use attributes of the Component's class, you can't directly use any external constants (or external variables).
The most elegant way that I've found so far is the following:
import { MY_CONSTANT } from '../constants';
#Component({
// ...
})
export class MyTestComponent implements OnInit {
readonly MY_CONSTANT = MY_CONSTANT;
// ...
}
which basically just creates a new attribute MY_CONSTANT inside the component class. Using readonly we make sure that the new attribute cannot be modified.
Doing so, in your template you can now use:
{{ MY_CONSTANT }}
The scope of Angular2 template bindings is the component instance. Only what's accessible there can be used in bindings.
You can make it available like
class MyComponent {
myConst = CONST;
}
{{myConst}}
There are two best directions in my opinion:
Wrapping constants as internal component property
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
readonly stateEnum : typeof stateEnum = stateEnum ;
}
Example uses enum, but this can be any type of defined constant. typeof operator gives you all of benefits of TypeScript typing features. You can use then this variable directly in templates:
component.html
<p>{{stateEnum.DOING}}<p>
This solution is less efficient in memory usage context, because you are basically duplicating data (or references to constants) in each component you wish to use it. Beside that, syntax
readonly constData: typeof constData = constData
in my opinion introduce a lot of syntax noise and may be confusing to newcommers
Wrapping external constant in component function
Second option is to wrap your external variable/constant with component function and use that function on template:
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
getEnumString(idx) {
return stateEnum[stateEnum[idx]];
}
}
component.html
<p>{{getEnumString(1)}}</p>
Good thing is that data is not duplicated in controller but other major downside occur. According to Angular team, usage of functions in templates is not recommended due to change detection mechanism, which works way less efficient in case of functions returning values to templates: change detection have no idea does value return by a function has changed, so it will be called way often than needed (and assuming you returning const from it, it's actually needed only once, when populating template view. It may be just a bit efficiency killing to your application (if you are lucky) or it may totally break it down if function resolves with Observable for instance, and you use async pipe to subscribe to results. You can refer to my short article on that HERE
You can create a BaseComponent , it is a place where you should create your constant instances and then you can create your FooComponent extends BaseComponent and you can use your constants.
I have a Vue component that does a number of complex tasks in mounted(). These tasks include for example, initializing Bootstrap Date Pickers, Time Pickers, and Typeaheads.
At the moment all of this initialization code is in my mounted() method. In order to understand what's going on, the developer has to read through the code comments.
Ideally I would move sections of code to their own methods, and only have method calls in mounted(), something such as:
mounted () {
this.helpers.initDatePickers();
this.helpers.initTimePickers();
this.helpers.initTypeaheads();
}
How can I achieve this? I realise that I can put them in the methods object, but I would prefer to leave that for methods which can be accessed via declarations in templates.
Note that I am not asking how to share helper functions across components (or globally). I am merely asking how to create local functions in their own space, in order to clean up some longer methods.
You could create a mixin module which has generic initialization.
// DatePickerMixin.js
import whatever from 'specific-date-picker-stuff';
export default {
methods: {
initDatePickers() {
// initialization here
}
}
}
Then your component just uses the mixin modules.
<script>
import DatePickerMixin from './mixins/DatePickerMixin';
import TimePickersMixin from './mixins/TimePickersMixin';
export default {
mixins: [
DatePickerMixin,
TimePickersMixin
],
data() {/* ... */},
// etc.
}
</script>
You could wrap all of these in the same mixin as well.
And if you don't want to always set the mixins, there's global mixin.
import DatePickerMixin from './mixins/DatePickerMixin';
Vue.mixin(DatePickerMixin);
Use global mixins sparsely and carefully, because it affects every
single Vue instance created, including third party components.
As #EmileBergeron said, mixins are a good solution. You can also create plugins, which so happen to encompass mixins as well but much more. They allow you to extend the Vue constructor or add instances/methods directly to the Vue prototype.
Section on plugins from the documentation
MyPlugin.install = function (Vue, options) {
// 1. add global method or property
Vue.myGlobalMethod = function () {
// something logic ...
}
// 2. add a global asset
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// something logic ...
}
...
})
// 3. inject some component options
Vue.mixin({
created: function () {
// something logic ...
}
...
})
// 4. add an instance method
Vue.prototype.$myMethod = function (methodOptions) {
// something logic ...
}
}
Using your plugin is done by:
// calls `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
// pass options to your plugin
Vue.use(MyPlugin, { someOption: true })
Here's a small plugin I recycle exposing various string functions in the pluralize library:
import {plural, singular, camelCase} from 'pluralize'
PluralizePlugin.install = function (Vue, options) {
Vue.plural = plural
Vue.singular = singular
Vue.camelCase = camelCase
}
With it you can use this.singular(str), this.plural(str), etc. throughout your components. Pretty simple but convenient.
So I'm writing this Aurelia application and one thing annoys me a lot. Say I have a custom component defined like this:
export class CustomComponent {
#bindable callbackForSomething;
#bindable anotherCallback;
}
Now, I have a couple of cases where I have to bind even more functions (or just anything else) on my component. So in each component I have code like this:
callbackForSomethingChanged() {
this._tryRunComponent();
}
anotherCallbackChanged() {
this._tryRunComponent();
}
_tryRunComponent() {
if (!this.callbackForSomething || !this.anotherCallback) {
return;
}
// run some logic here when I know the component is ready
}
Does AureliaJS have something to make this easier? With only two properties it's annoying, but I have components declaring a lot more properties.
Add a bind method to your view-model. It will be invoked by Aurelia once all of the bindable properties have been assigned.
Subsequent changes to the bindable properties will trigger your *Changed methods.
I'm moving from RequireJS to browserify (together with babelify) and try to rewrite my current modules to classes. For each of my RequireJS modules I have a method called eventHandler which handles all module specific events. Now when I extend a class, the parent class calls the subclass`s eventHandler method which leads to invoking the method twice.
Parent class:
'use strict';
class Tooltip {
constructor() {
this.eventHandler();
}
eventHandler() {
// Module specific events
}
}
module.exports = Tooltip;
Subclass:
'use strict';
import Tooltip from './Tooltip';
class Block extends Tooltip {
constructor() {
super();
this.eventHandler();
}
eventHandler() {
// Module specific events
// Gets called twice
}
}
module.exports = Block;
I liked the fact that the eventHandler method was named the same across all modules as it was easier to maintain. That's why I'd like to keep this pattern. So what would be the best way to solve this problem? Thanks for any suggestions!
Since you know the parent constructor calls this.eventHandler, don't do so in the derived classes:
'use strict';
import Tooltip from './Tooltip';
class Block extends Tooltip {
constructor() {
super();
// (No call here)
}
eventHandler() {
// Module specific events
// Gets called twice
}
}
module.exports = Block;
Re your comment:
The parent classes don't always implement an eventHandler method. So I need to ensure it gets called in this case as well.
Where they do, don't do the call. Where they don't, do do the call. Subclasses are very tightly bound to superclasses, by their nature.
If you can assume (!) that the presence of eventHandler in the super means it has called it from its constructor, you could do something like this:
constructor() {
super();
if (!super.eventHandler) {
this.eventHandler();
}
}
...but again, the nature of the super/sub relationship is that it's very tightly-bound, so relying on your knowledge of whether the super does this or not is reasonable.