How to inject custom service to angular component in plain ES5 (Javascript)? - javascript

I have a working angular2 Component.
I implemented a class for some service (using ng.core.Class if that matters).
What are the minimal steps to inject my service to my Component? Should I include my service in bootstrap function? Should I use any of ng.core.Inject or ng.core.Injectable? All my experiments failed so far.

You can do it super simple. Just create a class an pass it through providers property or through bootstrap
For example
// Alternative 1
var Service = ng.core.Class({
constructor : function() {},
someFunction : function() {
console.log('Some function');
}
})
// Alternative 2
var Service = function() {}
Service.prototype.someFunction = function() {
console.log('Some function');
}
Then pass it to the component
var Component = ng.core.
Component({
selector: 'cmp',
template : '',
providers : [Service]
}).
Class({
constructor: [Service, function(svc) {
svc.someFunction();
}]
});
Or through bootstrap
ng.platform.browser.bootstrap(Component, [Service]);
Here's an example so you can take a look at it.
Reference
Class (you can find some examples of its usage in the comments)

Related

Vue 2 - Communication between components (sending and receiving data)

So I working on app in Vue. I have problem with sending and receiving data between components. Already tried with $dispatch/$broadcast, $emit/$on but still now working. I want to send selected active_club from ClubSelection.vue to vue_main.js.
Vue version: 2.0.3
Structure of my app:
vue_main - main Vue file
HeaderElement.vue (child of vue_main)
ClubSelection.vue (child of HeaderElement)
Need to send active_club from ClubSelection to vue_main.
ClubSelection.vue
<script>
export default{
props: [
'club', 'title'
],
created(){
//Get club list
this.$http.get('/api/clubs', function(data) {
this.clubs = data;
console.log(data);
//read active club from parent
this.selected = this.$parent.$parent.active_club;
});
},
data(){
return{
clubs: [],
selected: null,
}
},
watch: {
selected: function(v) {
this.club = v;
//Post to database selected club
this.$http.post('/api/clubs/' + v + '/active')
},
club: function(v) {
this.selected = v;
//Change active_club at parent (THIS NOT WORKING)
// this.$emit('active_club', v);
// this.$parent.active_club = v;
club.$emit('active_club', v);
},
}
}
</script>
vue_main.js
const app = new Vue({
router,
data() {
return {
user: [],
active_club: null,
ranking: null
}
},
created: function() {
var self = this;
this.$http.get('/api/users/me', function(data) {
this.user = data;
self.active_club = data.active_club;
})
}
}).$mount('#app');
const club = new Vue();
//THIS NOT WORKING
club.$on('active_club', function (id) {
alert(id)
this.active_club = id;
});
Errors:
Vue warn]: Error in watcher "club" (found in component
)
vue_main.js:16924 Uncaught (in promise) ReferenceError: club is not
defined
I have tried many set ups, this is one of them. How to make this working?
$dispatch and $broadcast are deprecated in Vue 2.0.
In your case, what you need is communication between a parent component and child component. When a child $emits an event, parent can listen to it by providing a method in template markup itself, using v-on:parentMethod() as follows:
<child-component v-on:child-event="handlerMethod"></child-component>
The above markup is done inside parent component's template. And the parent component needs to have that handlerMethod in its methods.
Here is a sample "parent-child communication" question on Stackoverflow, which has a jsFiddle example also: Delete a Vue child component
You may use the above answer as reference to implement $emit in your app.
Edit: Additional Notes
I forgot to mention the note about three level hierarchy you have. In your app, you have the following hierarchy:
parent: vue_main
child 1: HeaderElement
child 1.1: ClubSelection
For sending events from ClubSelection to vue_main, you may either use non parent-child communication method or you can relay the event using the intermediate HeaderElement.
Here is how the event relay can work:
Step 1: ClubSelection sends a $emit, which is received by HeaderElement using v-on.
Step 2: The handlerMethod in HeaderElement does a this.$emit, which can be received by your main template using another v-on.
While the above may look a bit convoluted, it is much more efficient than broadcasting to every single component in the app, as it is generally done in Angualr 1.x or other frameworks.

Configure ui-router with components containing multiple bindings

I am trying to find a better solution to use the ui-router together with angular components.
Consider two simple components:
app.component('componentOne', {
template: '<h1>My name is {{$ctrl.name}}, I am {{$ctrl.age}} years old.</h1>',
bindings : {
name : '#',
age : '#'
}
}
);
app.component('componentTwo', {
template: '<h1>I am component 2</h1>'
});
Right now, I am specifying the component and its parameter using the template property:
app.config(function($stateProvider, $urlRouterProvider ){
$stateProvider
.state('component1', {
url: "/component1",
template: "<component-one name=\"foo\" age=\"40\"></component-one>"
})
.state('component2', {
url: "/component2",
template: "<component-two></component-two>"
})
});
While this is working fine, I have components with arround ten bindings which makes the configuration of the ui-router realy awkward.
I tried using the component property but this doesn't work for me at all. The only other solution I found is to specify the parent using the require property and omit the bindings - but this doesn't feel right for me... Is there a better way to do this?
Here is a plnkr.
UI-Router component: routing exists in UI-Router 1.0+ (currently at 1.0.0-beta.1)
Here's an updated plunker: https://plnkr.co/edit/VwhnAvE7uNnvCkrrZ72z?p=preview
Bind static values
To bind static data to a component, use component and a resolve block which returns static data.
$stateProvider.state('component1', {
url: "/component1",
component: 'componentOne',
resolve: { name: () => 'foo', age: () => 40 }
})
Bind async values
To bind async values, use a resolve which returns promises for data. Note that one resolve can depend on a different resolve:
$stateProvider.state('component1Async', {
url: "/component1Async",
component: "componentOne",
resolve: {
data: ($http) => $http.get('asyncFooData.json').then(resp => resp.data),
name: (data) => data.name,
age: (data) => data.age
}
});
Bind lots of values
You mention you have 10 bindings on a component. Depending on the structure of the data you're binding, you can use JavaScript to construct the resolve block (it's "just javascript" after all)
var component2State = {
name: 'component2',
url: '/component2',
component: 'componentTwo',
resolve: {
data: ($http) => $http.get('asyncBarData.json').then(resp => resp.data)
}
}
function addResolve(key) {
component2State.resolve[key] = ((data) => data[key]);
}
['foo', 'bar', 'baz', 'qux', 'quux'].forEach(addResolve);
$stateProvider.state(component2State);
Alternatively, you can move your bindings a level down and create an object which will be the only bindings. If 10 bindings is what is bothering you.
One alternative you can try is to override the template by custom properties of states in $stateChangeStart event.
Run block like this to achieve this kind of behaviour.
app.run(function($rootScope){
//listen to $stateChangeStart
$rootScope.$on("$stateChangeStart",function(event, toState, toParams, fromState, fromParams, options){
//if the component attribute is set, override the template
if(toState.component){
//create element based on the component name
var ele = angular.element(document.createElement(camelCaseToDash(toState.component)));
//if there is any binding, add them to the element's attributes.
if(toState.componentBindings){
angular.forEach(toState.componentBindings,function(value,key){
ele.attr(key,value)
})
}
//you may also do something like getting bindings from toParams here
//override the template of state
toState.template = ele[0].outerHTML;
}
})
//convert camel case string to dash case
function camelCaseToDash(name) {
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
})
And with this now you can have component property in your state config.
app.config(function($stateProvider, $urlRouterProvider ){
$stateProvider
.state('component1', {
url: "/component1",
component:"componentOne",
componentBindings:{
name:"foo",
age:"40",
}
})
.state('component2', {
url: "/component2",
component:"componentTwo",
})
});
Here is the working plunker.
Still you may have a large config function, but it will look not so awkward.

Testing Vue components methods

I am starting with Vue.js and is really hard to find documentation about Unit Test.
I am trying to test components methods and builtin stuff as ready(). I can call those correctly but they internally have references to this object and this context is lost during testing time.
error
TypeError: this.$on is not a function
spec.js
import Vue from 'vue';
import Partners from 'components/main/partner/Partners';
describe.only('Partners.vue', () => {
it('should render with mocked partners', (cb) => {
Partners.ready(); // I get an error here because ready() is calling inside: this.$on(...)
cb(null);
});
});
component.vue
export default {
name: 'Partners',
data() {
return { };
},
methods: {
get() {
// ...
}
},
ready() {
this.$on('confirm', (confirm) => {
// ...
});
this.get();
}
};
I think ready() is depreciated along with Vue 1.0. Consider upgrading to Vue 2 (where mounted() replaces ready()).
To test your component, you need to initialize your component and a Vue instance, (and usually mount it, depending what you are doing).
Using the vue-webpack template (which uses Vue 2):
var ctor = Vue.extend(Partners)
var vm = new ctor()
vm.$mount()
now you can do things like vm.method(), and vm.mount() will automatically be called, etc.

Angular2 + Angular1 + ES5 syntax component subscription

First of all let me say I am still using ES5, mostly because I am writing this for a frontend of a Google Apps Scripts application and didn't have the time/patience to make TypeScript work.
I am currently using this method in order to upgrade my Angular1 app to Angular2:
http://www.codelord.net/2016/01/07/adding-the-first-angular-2-service-to-your-angular-1-app/
I have a overlayLoaderService service to show a loading spinner in an overlay div with simple functions to get and set the loading state, and a overlayLoader component to show the div itself.
Service:
var overlayLoaderService = ng.core.
Injectable().
Class({
constructor: function() {
this.loading = false;
this.stateChange = new ng.core.EventEmitter();
},
setState: function(state) {
this.loading.value = state;
this.stateChange.emit(state);
},
getState: function() {
console.log(this.loading);
}
});
upgradeAdapter.addProvider(overlayLoaderService);
angular.module('Gojira').factory('overlayLoaderService', upgradeAdapter.downgradeNg2Provider(overlayLoaderService));
Component:
var OverlayLoaderComponent = ng.core
.Component({
selector: 'overlay-loader',
template: '<div [hidden]="loading" id="overlay-loader"></div>',
providers: [overlayLoaderService]
}).Class({
constructor: [overlayLoaderService, function(overlayLoaderService) {
this.loading = !overlayLoaderService.loading.value;
this._subscription = overlayLoaderService.stateChange.subscribe(function (value) {
console.log(value);
this.loading = !value;
});
}],
});
angular.module('Gojira').directive('overlayLoader', upgradeAdapter.downgradeNg2Component(OverlayLoaderComponent));
What I am trying to do is to achieve is the component to update its loading property when I call setState() method in the overlayLoaderService.
The subscription is never called, so I guess I am doing something terribly wrong here.
Any help would be appreciated.
remove
providers: [overlayLoaderService]
from OverlayLoaderComponent should work, as the provider has already been added to the adapter. Otherwise, it seems like angular 1 component and angular 2 component are using different instance of that service.

Extending a base class in an Angular service

I have a base class that I would like to extend in a service to help get data in to the angular scope. I have searched around the net for a solution, but have not found one that I like. I have a base class that is used to access the File systems of devices
the class structure:
var cOfflineStorageBase = Class.extend({
init: function(){
},
CreateFolderDir: function(){
},
DeleteAll: function(){
},
DeleteDirectories: function(){
},
DeleteItem: function(){
},
GetFiles: function(){
},
FileExists: function(){
},
GetPath: function(){
},
GetObject: function(){
},
SaveObject: function(){
},
});
I would like to be able to extend this class in several different angular services (ie offlineCart, offlineCustomLists, ect...) where each service would be able to use the storage base to store the various different data types. I am looking for the best, most appropriate way to do this in angular. In vanilla JavaScript one would just do something like this:
var newClass = cOfflineStorageBase.extend({
//add new stuff here
});
but I want to do this same thing the angular way.
The approach I have been considering are to use the angular.extend functionality, but I am not sure this is appropriate or would something like this be a more appropriate approach:
app.factory('someChild', ['$http' , 'cOfflineStorageBase',
function($http, cOfflineStorageBase){
var SomeClass = cOfflineStorageBase.extend({
init: function(){
this._super.init()
},
//Add more stuff here
});
return SomeClass;
}]);
I would like some advice if theses approaches are correct or if there might be another that is better for what I am wanting to accomplish. I would also like or rather need to use promises in much of this code as it would be async.
I pulled off this trick recently.
I will start by defining a plain JavaScript constructor. This does not need to be an angular service. What I do is that, later, the extending constructors can pass any necessary injections by parameter. So, this will be the base "class" of my angular services. This is where I would expose anything I want all angular services to inherit.
function ParentService($http) {
this.$http = $http;
}
ParentService.prototype.foo = function () {
alert("Hello World");
};
Then I will proceed to define a child constructor using prototypal inheritance. This constructor will indeed be an angular service (you can tell by my use of $inject at the end).
function ChildService($http) {
Parent.call(this, $http);
}
ChildService.prototype = new ParentService();
ChildService.prototype.baz = function() {
return this.$http.get('/sample/rest/call');
}
ChildService.$inject = ['$http'];
Then I will proceed to register the services à la carte in the corresponding angular modules:
var app = angular.module('SampleApp', []);
app.service('child', ChildService);
Finally, in my controller I will simply inject my service, which will be an instance of my ChildService constructor, which in turn extends my ParentService constructor:
app.controller('MainCtrl', ['$scope', 'child', function ($scope, child) {
child.foo(); //alert("Hello World")
var promise = child.bar();
}]);
You can see a JSFiddle here
Also there is an interesting video in Youtube from ngConf called Writing A Massive Angular App which covers some of these topics and a few other ideas on code reusability with angular.
This question was asked, and answered, 18 months ago. I recently went through the same issue on a project. I wanted to have a base Model defined that I could use to build factories off of. Angular has a very simple Provider to assist with this called the Value Provider, which Angular implements using the Value Recipe.
I'm not sure what version of Angular you may have been using at the time, but this dates back (AFAIK) to version 1.3.0. (As of this writing, current stable is 1.4.8)
I'm also using John Resig's Simple Inheritance Script.
http://ejohn.org/blog/simple-javascript-inheritance/
Here's a snippet of my code (with most of the application specific logic removed).
var MyApp = angular.module( 'MyApp',['ngResource','ngAnimate','ngSanitize'] );
/* ==================================================================================== */
// - Base Model Class -------------------------------------------------------------
/* ==================================================================================== */
MyApp
/**
* BaseModel - Value Provider
*
*/
.value( 'BaseModel',Class.extend({
attribs: {},
init: function(){
var self = this;
_active = true;
_new = true;
_origs = {};
_loadByObject = function( obj ){ ... }
},
get: function( key ){ ... },
set: function( key,val ){ ... },
isNew: function(){ ... },
keep: function(){ ... },
remove: function(){ ... },
load: function( obj ){ ... }
verify: function(){ ... },
save: function(){ ... },
}))
.factory( 'UserFactory',
[ '$http', '$q', 'BaseModel',
function( $http, $q, BaseModel ){
var UserFactory = BaseModel.extend({
init: function(){
this._super( false );
_fields = [
'first', 'last', 'email',
'phone', 'password', 'role'
];
_permitted = [
'first', 'last', 'email',
'phone', 'password', 'role'
];
_required = [
'first', 'last', 'email', 'role'
];
_resource = "users";
_api = "users";
}
});
return UserFactory;
}])
I'd love to hear anyone's feedback, too.
Here's the Angular
Docs:
https://code.angularjs.org/1.3.0/docs/guide/providers

Categories