ng-lit v0.2.15
:warning: Under development :hammer_and_wrench:
π Boost Your Old AngularJS App LitElement Components.
Are you stuck maintaining a crufy old angularjs 1.x app? Wish you could be writing straightforward component-based views but can't afford to move the whole thing to the Fancy New Frameworkβ’οΈ? With LitElement and some helpers from ng-lit, you can incrementally update your old app piece by piece.
π©βπ Installing
npm i -S ng-litng-lit lets you pass objects and arrays from your AngularJS application into your lit-element views without parsing or watching them yourself.
π¨βπ» Usage
Your New lit-element View
import { LitElement, html } from "lit-element";
import { NgLit } from "ng-lit";
class NgLitUser extends NgLit(LitElement) {
static get properties() {
return {
age: { type: Number },
user: { type: Object }
};
}
// declare the angular props
static get ngProps() {
return {
user: { default: {} }
}
}
render() {
const { age, user } = this;
return html`
<span>${user.firstName} ${user.lastName} is ${age} years old</span>
`;
}
}
customElements.define('ng-lit-user', NgLitUser);Your AngularJS App:
<!-- angular -->
<div ng-app="myApp"
ng-controller="myCtrl">
<ng-lit-user
user="ngUser"
age="15">
</ng-lit-user>
</div>
<script>
angular.module('myApp', [])
.controller('myCtrl', $scope => {
$scope.ngUser = {
firstName: "John",
lastName: "Doe"
};
});
</script>π§ββοΈ Motivation
ng-lit allows you to bring your old AngularJS application up to date piece-by-piece, using lit-element components to refactor from the bottom-up.
Web components work out-of-the-box in angular templates, but due to some quirks in angular's data binding system, it can sometimes be awkward passing your app's data back down into your web components. ng-lit helpers make it easier for your new lit-element components to live side by side with old AngularJS code, until you can fully drop AngularJS from the app.
Conceptual example
Consider an angularjs todo-app composed of three components:
- Main app entrypoint that loads a list of
todoobjects on to the$scope.<todo-main-app> - Component that get a list of
todoobjects and and renders<todo-item>s for each one.<todo-list todos="vm.myTodoList"> - Component that get a single
todoobject and render it's text andisDonestate.<todo-item todo="vm.singleTodo">
For your first incremental change, you can use ng-lit to build a new implementation for <todo-item> based on lit-element with same interface as the old angularjs directive.
<lit-todo-item todo="vm.singleTodo">You can stop here and the app will still work fine. When you're ready to move on, you can continue by upgrading <todo-list> and finally <todo-main-app>.
π¨βπ« API
Reactive
Dy default ng-lit will render your component on changes made to object or array's reference.
The following example will update your element when $scope.myBook is updated with new object:
class NgBook extends NgLit(LitElement) {
static get properties() {
return {
book: { type: Object }
};
}
static get ngProps() {
return {
book: { type: Object }
}
}
render() {
const { book } = this;
return html`
<span>${book.title} by ${book.author}</span>
`;
}
}
customElements.define('ng-lit-book', NgBook);<!-- angular -->
<div ng-app="myApp" ng-controller="myCtrl">
<ng-lit-book book="myBook"></ng-lit-book>
<button ng-click="selectBook({title: 'Anna Karenina', author: 'Leo Tolstoy'})">
Anna Karenina
</button>
<button ng-click="selectBook({title: '1984', author: 'George Orwell' })">
1894
</button>
</div>
<script>
angular.module('myApp', [])
.controller('myCtrl', $scope => {
$scope.myBook = null;
$scope.selectBook = book => {
$scope.myBook = book;
}
});
</script>Properties
When you want your component to get certain props, add them to the ngProps static getter. You still have to define those properties in the regular lit-element static properties getter. The idea is that eventually you'll remove angular from your app entirely, at which point you'll just need to remove the ngProps block;
The following example will fetch a list of books and a selectedBook object from angular while userId will be treated as normal custom element property, without special arrangements for angularjs' data system.
static get properties() {
return {
userId: { type: Number },
books: { type: Array },
selectedBook: { type: Object }
};
}
static get ngProps() {
return {
books: { default: [] },
selectedBook: { default: {} }
}
}Defaults
use the default option to pass a default value which your prop will get in case angular doesn't have that value in scope, or the value found was null.
static get ngProps() {
return {
selectedBook: { default: { title: '1984', author: 'George Orwell' } }
}
}If you pass an object as the default, it will be cloned before it's assigned to the instance.
Watch
Set the watch boolean option to make your element update when angular changes the property.
The following example will update your element when $scope.addBook() is called:
class NgLitBookList extends NgLit(LitElement) {
static get properties() {
return {
books: { type: Array }
};
}
static get ngProps() {
return {
books: { default: [], watch: true }
}
}
render() {
const { books } = this;
return html`
<ul>
${books.map(({title, author}) => html`
<li>${title} by ${author}</li>
`)}
</ul>
`;
}
}
customElements.define('ng-lit-books', NgLitBookList);<!-- angular -->
<div ng-app="myApp" ng-controller="myCtrl">
<ng-lit-books books="myBooks"></ng-lit-books>
<button ng-click="addBook({title: 'Anna Karenina', author: 'Leo Tolstoy'})">
Anna Karenina
</button>
</div>
<script>
angular.module('myApp', [])
.controller('myCtrl', $scope => {
$scope.myBooks = [];
$scope.addBook = book => {
$scope.myBooks.push(book)
}
});
</script>π¨π½βπ» Unit Test your components
We recommend using @open-wc/testing-helpers for unit testing your Web Components.
In order to Unit Test ng-lit component we just need to mock angular scope, to do so we expose MockScope API.
The following example will text NgLitBookList component:
import '/components/NgLitBookList.js'
import {fixture, html} from '@open-wc/testing-helpers';
import {MockScope} from 'ng-lit/mock';
describe('ng-lit-books', async () => {
it('should render component with 2 books', async () => {
// Mock angular's scope with 2 books
MockScope({myBooks: [
{title: 'Anna Karenina', author: 'Leo Tolstoy'},
{title: '1984', author: 'George Orwell' }
]});
const {shadowRoot} = await fixture(html`
<ng-lit-books books="myBooks">
</ng-lit-books>
`);
const renderedBooks = shadowRoot.querySelectorAll('li');
expect(renderedBooks.length).to.equal(2);
expect(renderedBooks[0]).to.equal('Anna Karenina by Leo Tolstoy');
expect(renderedBooks[1]).to.equal('1984 by George Orwell');
})
});π¨π½βπ» Developing
Installation
git clone git@github.com:oriweingart/ng-lit.git
cd ng-lit
npm iRun Locally
npm run devRun demo examples
npm run demoRun end-to-end tests
npm run test:e2eRun unit tests
npm run test:unitRun both unit and e2e tests
npm run testRun Lint
npm run lint7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago