Angular JS 1.X Component Inheritance - angularjs

How does one achieve inheritance with Angular JS components? My sample is:
app.component('ResourceForm', controller: [
function () {
this.save = () => {
$http(this.path, this.attributes());
};
},
]);
app.component('PersonForm', {
bindings: {
person: '<person',
},
controller: [
function () {
this.path = '/person/' + this.person.id;
this.attributes = () => { name: this.name };
},
],
});
<!-- templates/person_form.html -->
<form>
<input type="text" ng-model="$ctrl.name" >
<submit ng-click="$ctrl.save()"></submit>
</form>

As far as I know there is no real inheritance. You can play with configs to get some kind of inheritance:
var cfg = {
bindings: {
person: '<person',
age: '#',
onNameChange: '#'
}
}
var component1 = angular.copy(cfg);
component1.controller = ctrl1;
app.component('component1', component1);
var component2 = angular.copy(cfg);
component2.controller = ctrl1;
app.component('component2', component2);

Related

Using AngularJS component props

I'm new to angularJS, and now I'm trying to realize some parts.
The questions is: how do I get access to callback onFinish() which is passed to component "my-timer" and run it? this.onFinish() returns the error.
Here is my markup:
<div ng-app="app" ng-controller="MyCtrl as myCtrl">
<div>
Status: {{myCtrl.status ? myCtrl.status : 'Waiting...'}}
</div>
<div>
<button ng-click="myCtrl.addTimer(5)">Add timer</button>
</div>
<div ng-repeat="timer in myCtrl.timers">
<div>
<h3>Timer {{timer.id}}</h3>
<button ng-click="myCtrl.removeTimer($index)">X</button>
<my-timer id="{{timer.id}}" start-seconds="{{timer.seconds}}" on-finish="myCtrl.onFinish(endTime)"></my-timer>
</div>
</div>
</div>
And here is index.js
var app = angular.module('app', []);
app.controller('MyCtrl', class {
constructor($scope) {
this.status = null;
this.timerId = 0;
this.timers = [];
this.addTimer(10);
this.addTimer(3);
console.log($scope);
}
addTimer(seconds) {
this.timers.push({
id: this.timerId++,
seconds
});
}
removeTimer(index) {
this.timers.splice(index, 1);
}
onFinish(endTime){
this.status = `Timer finished at ${endTime}`;
console.log(endTime);
}
});
app.component('myTimer', {
bindings: {
id: '#',
startSeconds: '#',
onFinish: '&',
},
controller: function($interval, $scope) {
this.endTime = null;
this.$onInit = function() {
this.countDown();
};
this.countDown = function() {
$interval(() => {
this.startSeconds = ((this.startSeconds - 0.1) > 0) ? (this.startSeconds - 0.1).toFixed(2) : 0;
}, 100);
};
},
template: `<span>{{$ctrl.startSeconds}}</span>`,
});
And here is jsFiddle
this.$onInit = function() {
this.countDown();
};
this.onFinish('1');
The problem here is that you tried to execute this.onFinish right in controller's body. And that wont work that way. If you want this function to be called during initialization, move it to $onInit
this.$onInit = function() {
this.countDown();
this.onFinish('1');
};
Otherwise, call it from another component method. You can only declare variables and component methods in controller body, but not call functions.

What is wrong with my Typescript AngularJS 1.5 component?

I need a custom AngularJS 1.5x component (in TypeScript!) for entering a date and time, that will have some required time zone correction logic, like this:
<date-time value="vm.woComplete.CompleteDate"></date-time>
where vm.woComplete.CompleteDate is the date property of my data model.
TypeScript code for this component is this:
namespace AppDomain {
"use strict";
export class DateTimeComponent {
require: string = "ngModel";
bindings: any = { value: "=" };
template: string = "<input type='date' ng-model='$ctrl.displayValue'><input type='time' ng-model='$ctrl.displayValue'></p>";
controller: Function = function ($scope) {
var ctrl = this;
ctrl.$onInit = function () {
console.log(arguments);
ctrl.displayValue = new Date(ctrl.value);
ctrl.displayValue.setHours(ctrl.displayValue.getHours() - 4);
}
$scope.$watch('$ctrl.displayValue', function () {
var tmpDate = new Date(ctrl.displayValue);
ctrl.value = tmpDate.setHours(tmpDate.getHours() + 4);
});
}
}
app.component("dateTime", DateTimeComponent);
}
But I don't get any output. What am I doing wrong?
I want to have proper TypeScript code with $onInit and $onChangesinstead of $scope.$watch.
Here is the example, just need to convert it TypeScript!
http://plnkr.co/edit/D9Oi6le7G9i3hNC2yvqy?p=preview
You use a syntax I have never seen, and what is DetailController?
Try with this syntax instead:
export class Controller {
value: number;
displayValue: Date;
$onInit() {
this.displayValue = new Date(this.value);
this.displayValue.setHours(this.displayValue.getHours() - 4);
};
$onChanges() {
const tmpDate = new Date(this.displayValue);
this.value = tmpDate.setHours(tmpDate.getHours() + 4);
};
}
const component : angular.IComponentOptions = {
controller : Controller,
bindings: { value: "=" },
template: `
<p><label>Date:</label><br>
<input type='date' ng-model='$ctrl.displayValue'></p>
<p><label>Time:</label><br>
<input type='time' ng-model='$ctrl.displayValue'></p>`
};
app.component("dateTime", component);

Angular. Pass data from component to parent controller

Hot to recive data from component in parent controller.
I have this code:
index.html
<div ng-controller="formController">
<phones-list phone="phone"></phones-list>
<button ng-click="saveForm()">Save</button>
</div>
form.controller.js
var app = angular.module('myApp');
app.controller('formController', ['$scope', function($scope) {
$scope.saveForm = function() {
console.log($scope.phone)
}
}]);
phoneList.component.js
var app = angular.module('myApp');
app.component('phonesList', {
templateUrl: '/scripts/components/phones/phonesList.template.html',
controller: 'phonesListController',
bindings: {
phone: '='
}
});
phoneList.template.html
<select name="" id="" ng-change="$ctrl.change()" ng-model="$ctrl.phone">
<option ng-repeat="phone in $ctrl.phones">{{ phone.name }}</option>
</select>
phoneList.controller.js
var app = angular.module('myApp');
app.controller('phonesListController', function() {
this.phones = [
{
name: 'ABC',
number: '723-543-122'
},
{
name: 'ABCDE',
number: '324-531-423'
}
];
this.change = function() {
console.log(this.phone)
}
});
So I have select list with phones. What I want is to get phone object in formController after select and submit form. For now I get only text value from .
Add an additional binding to your phoneList component with a function to call when the selection changes.
phoneList.component.js
var app = angular.module('myApp');
app.component('phonesList', {
templateUrl: '/scripts/components/phones/phonesList.template.html',
controller: 'phonesListController',
bindings: {
phone: '=',
onChange: '&' //This will allow you to bind a function to your
} //component that you can execute when something
}); //happens
phoneList.controller.js
var app = angular.module('myApp');
app.controller('phonesListController', function() {
this.phones = [
{
name: 'ABC',
number: '723-543-122'
},
{
name: 'ABCDE',
number: '324-531-423'
}
];
this.change = function() {
console.log(this.phone);
this.onChange({phone: phone}); //call our new callback and give it
} //our update
});
index.html
<div ng-controller="formController">
<phones-list phone="phone"
on-change="phoneSelected(phone)">
<!--Tell our component which function we wish to bind to-->
</phones-list>
<button ng-click="saveForm()">Save</button>
</div>
form.controller.js
var app = angular.module('myApp');
app.controller('formController', ['$scope', function($scope) {
$scope.saveForm = function() {
console.log($scope.phone)
}
$scope.phoneSelected(phone){
//Do your stuff here!
}
}]);
I hope that helps!
I found the solution. I changed component template and add ng-options directive to . I don't know why it is more diffucult to do the same in standard list.
index.html
<div ng-controller="ProposalController">
<phones-list phone="phone"></phones-list>
<button ng-click="saveForm()">Zapisz</button>
</div>
phoneList.component.js
var app = angular.module('myApp');
app.component('phonesList', {
templateUrl: '/components/phones/templates/phoneList.template.html',
controller: 'PhoneListController',
bindings: {
phone: '='
}
});
phoneList.controller.js
....
this.change = function() {
this.phone = this.selectedPhone;
}
....
phoneList.template.html
<select ng-model="$ctrl.selectedPhone" ng-change="$ctrl.change()" ng-options="phone.name for phone in $ctrl.phones"></select>
form.controller.js
$scope.saveForm = function(){
console.log($scope.phone)
}

Example of injecting services in Angular 1.5 components

Can anyone give an example on using services with Angular 1.5 components?
I'm trying to inject a service in an Angular 1.5 component, but it doesn't work.
I have a login component like so:
class Login {
constructor($scope, $reactive, $state, myService) {
console.log(myService.somevariable); //doesn't work
}
}
// create a module
export default angular.module(name, [
angularMeteor
]).component(name, {
templateUrl: 'imports/ui/components/${name}/${name}.html',
controllerAs: name,
controller: Login
});
My service looks like this:
angular.module(name).service("myService", function () {
this.somevariable = 'somevalue';
});
I just cant seem to be able to get the service injected in the component.What am I doing wrong?
SOLUTION:
With sebenalern's help, I got it working.
I needed a service to validate an email address using a regular expression. I did it like this:
import angular from 'angular';
import angularMeteor from 'angular-meteor';
class Validator {
validateEmail(email) {
var re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}
}
const name = 'validator';
// create a module
export default angular.module(name, [
angularMeteor
])
.service("Validator", Validator);
I then injected the service like so:
import {name as Validator} from '../../../api/services/validator'
class Login {
constructor($scope, $reactive, $state, Validator) {
'ngInject';
this.$state = $state;
$reactive(this).attach($scope);
this.Validator = Validator;
}
login() {
if(this.Validator.validateEmail(this.credentials.email)) {
// email is valid.
}
}
}
const name = 'login';
export default angular.module(name, [
angularMeteor,
Validator
]).component(name, {
templateUrl: `imports/ui/components/${name}/${name}.html`,
controllerAs: name,
controller:Login
})
Hope this helps :)
So one problem I see is you should be using the keyword this inside the constructor
this.$scope = $scope;
Another thing it is probably easier to stay away from classes and use functions:
class Login {
constructor($scope, $reactive, $state, myService) {
console.log(myService.somevariable); //doesn't work
}
}
Becomes:
angular
.module('name')
.service('myService', myService);
function myService () {
this.somevariable = 'somevalue';
}
To me it seems a lot cleaner. Also another thing about ES6 classes is
ES6 Classes are not hoisted, which will break your code if you rely on hoisting
For more info see link.
Now here is the working code I came up with:
First we declare the module:
angular.module('name', []);
Next we register our service and then create the service definition:
angular
.module('name')
.service('myService', myService);
function myService () {
this.somevariable = 'somevalue';
}
Next we do the same procedure for our controller and also we inject $scope and our service into it.
angular
.module('name')
.controller('Login', Login);
function Login($scope, myService) {
$scope.someVar = myService.somevariable;
}
Last I registered our component:
angular
.module('name')
.component('my-html', {
templateUrl: 'my-html.html',
controller: Login
});
And that is it on the javascript side.
Here is my html code:
<!DOCTYPE html>
<html lang="en-us" ng-app='name'>
<head>
<script src="//code.angularjs.org/1.5.0-rc.1/angular.js"></script>
<script src="controller.js"></script>
</head>
<body >
<h ng-controller="Login">{{ someVar }}</h>
</body>
</html>
I hope this helps!!
Here is how I am doing it and it works well. It works fine with classes. I assume you are using TypeScript.
class AdminHomeService {
consignment: IConsignment;
get: () => IConsignment;
constructor() {
this.consignment = new Consignment();
this.consignment.id = 10;
this.consignment.customer = "Customer3";
this.consignment.customerList = [{ id: 1, name: "Customer1" }, { id: 2, name: "Customer2" }, { id: 3, name: "Customer3" }];
this.consignment.shipperList = [{ key: "1", value: "Shipper1" }, { key: "2", value: "Shipper2" }, { key: "3", value: "Shipper3" }];
this.consignment.consigneeList = [{ key: "1", value: "Consignee1" }, { key: "2", value: "Consignee2" }, { key: "3", value: "Consignee3" }];
this.consignment.billingList = [{ key: "1", value: "Billing1" }, { key: "2", value: "Billing2" }, { key: "3", value: "Billing3" }];
this.consignment.carrierList = [{ key: "1", value: "Carrier1" }, { key: "2", value: "Carrier2" }, { key: "3", value: "Carrier3" }];
this.get = () => {
return this.consignment;
}
}
}
class AdminHomeComponentController {
consignment: IConsignment;
selectedCustomer: any;
static $inject = ["adminHomeService"];
constructor(private adminHomeService: AdminHomeService) {
this.consignment = new Consignment();
this.consignment = this.adminHomeService.get();
this.selectedCustomer = {};
this.selectedCustomer.selected = { "name": this.consignment.customer };
}
customerAddClick(): void {
}
}
class AdminHomeComponent implements ng.IComponentOptions {
bindings: any;
controller: any;
templateUrl: string;
$routeConfig: angular.RouteDefinition[];
constructor() {
this.bindings = {
textBinding: "#",
dataBinding: "<",
functionBinding: "&"
};
this.controller = AdminHomeComponentController;
this.templateUrl = "templates/admin.home.html";
//this.$routeConfig = [
// { path: "/admin", name: "AdminHome", component: "adminHome", useAsDefault: true }
//];
}
}
angular.module("adminHome", [])
.component("adminHome", new AdminHomeComponent())
.service("adminHomeService", AdminHomeService);
This post helped me a lot: http://almerosteyn.com/2016/02/angular15-component-typescript

RxJs and Angular1 component - how to avoid $scope?

Pseudo code for angular 1.5 component with RxJs:
component('demo', {
template: `<div>
<div ng-if="verificationFailed">Sorry, failed to verify</div>
<button ng-if="continueEnabled">Continue</button>
<button ng-click="verify()">Verify</button>
</div>`,
controllerAs: 'ctrl',
bindings: {
someOptions: '='
},
controller: ($scope, someService) => {
var ctrl = this;
ctrl.continueEnabled = false;
ctrl.verificationFailed = false;
ctrl.verify = function() {
Rx
.Observable
.interval(10 * 1000)
.timeout(2 * 60 * 1000)
.flatMapLatest(_ => { someService.verify(ctrl.someOptions.id)})
.retry(1)
.filter((result) => { result.completed })
.take(1)
.subscribe(_ => {
$scope.$evalAsync(_ => {
ctrl.continueEnabled = true
});
}, _ => {
$scope.$evalAsync(() => {
ctrl.verificationFailed = true;
});
});
};
}
});
Any way to avoid using $scope with $evalAsync to trigger digest? Without it the view is simply not updating.
Why? Because there is no $scope on angular2 and i want to make migration as easy as it is possible
You can use angular1-async-filter. Take a look at this good article:
http://cvuorinen.net/2016/05/using-rxjs-observables-with-angularjs-1/
Here is an example:
(function(angular) {
var myComponent = (function () {
function myComponent() {
this.template = "<div><br/> Time: {{ctrl.time | async:this}}</div>";
this.controllerAs = 'ctrl';
this.controller = "myController";
}
return myComponent;
}());
var myController = (function() {
function myController() {
this.time = Rx.Observable.interval(1000).take(50);
}
return myController;
}());
angular.module('myApp', ['asyncFilter']);
angular.module('myApp').component('myComponent', new myComponent());
angular.module('myApp').controller('myController', myController);
})(window.angular);
See it working on Plunker:
https://plnkr.co/edit/80S3AG?p=preview

Resources