ngparty / ng-metadata Goto Github PK
View Code? Open in Web Editor NEWAngular 2 decorators and utils for Angular 1.x
Home Page: https://hotell.gitbooks.io/ng-metadata/content/
License: MIT License
Angular 2 decorators and utils for Angular 1.x
Home Page: https://hotell.gitbooks.io/ng-metadata/content/
License: MIT License
requiring other directive controllers is really cumbersome via angular 1 ddo require: string | string[]
property.
This is resolved in 1.5 via Map support for require: string | string[] | {[boundName:string]:any}, but only works with
ngModule.component`.
Current DI via @Inject has to rely on name provided for require array, which is pretty inconvenient to use ( your property has to have same name as directive ).
because those reasons, we need to require for other directive instances manually as follows:
require: ['ngModel']
=> $element.controller('ngModel')
require: ['^ngModel']
=> $element.controller('ngModel') || (while(notFound){ var notFound = $element.parent().controller('ngModel')
})require: ['^^ngModel']
=> while(notFound){ var notFound = $element.parent().controller('ngModel')
}all of above should throw and Error if controller not found == same behaviour as ng1 require
require: ['?ngModel']
global
without hacks and will remove the need of import global.d.ts
to ng-metadata/core
during buildin angular it's possible to inject same type of directives by require directive on itself and on parent via:
{require:['ngModel','^^ngModel']}
If I try do that within ngMetadata constructor I've got unefined formProvider
class SomeAttrDirective{
constructor(
@Inject('form') @Self() form,
@Inject('form') @SkipSelf() parentForm
){}
}
@Query
support implementation #39Maybe what this project need is a live (static) site. As landing page for this project and a marketing device for this project. (With marketing device I mean a link to a few nice pages that could be dropped in blog posts, ... :) )
I was thinking maybe you could use a gh-pages site to show the api docs, examples, introduction and getting started material, ...
I'm not sure what your opinion is but wouldn't it be nice to let something like jsdocs generate your api docs. Which could then be picked up by the gh-pages site?
I'm also not sure what your opinion is about how much docs should live in the actual source code.
Anyways I definitely want to help with these things. Just let me know what you think.
how should it look and work like?
I see 2 approaches here:
and after thorough research a way to go is number 2!
See results
@Component({})
class Foo(){}
behind the scenes it will:
angular.module('foo',[])
andangular.module('foo').directive(makeSelector(Foo),makeDirective(Foo))
// user.component.ts
import {Component,Input} from 'ng-metadata';
@Component({
selector: 'user',
template: `
<div>
<h1>User detail !</h1>
<ul><li>twitter: {{ ctrl.user.twitter }}</li></ul>
</div>
`
})
class User{
@Input() user: {twitter:string}
greet(){
return `${this.user.twitter} says Hello yo!`;
}
}
// user.component.spec.ts
import {User} from './user.component';
import {getControllerName, getModule} from 'ng-metadata';
describe('User Component', ()=>{
beforeEeach(()=>{
angular.mock.module(getModule(User));
});
var userCtrl;
beforeEach(inject(($controller)=>{
userCtrl = $controller(getControllerName(User),{},{user:{twitter:'foobar'}});
}));
it('should get passed user via input binding',()=>{
expect(userCtrl.user).to.deep.equal(user:{twitter:'foobar'}))
});
it('should greet',()=>{
expect(userCtrl.greet()).to.equal('foobar says Hello yo!');
});
})
should translate to:
class User{
static requiredModules = [];
static selector = 'user';
static template = `
<div>
<h1>User detail !</h1>
<ul><li>twitter: {{ ctrl.user.twitter }}</li></ul>
</div>
`;
static options = {
bindToController: {
userInput:'='
}
};
user = null;
}
function userDirectiveFactory(){
return {
scope: {},
bindToController: User.options.bindToController,
controller: User,
controllerAs: 'ctrl',
template: App.template,
link: (scope,element,attrs,ctrl)=>{
// one way binding
scope.$watch(()=>ctrl.userInput,(user)=>{
ctrl.user = angular.copy(user)
});
if(angular.isFunction(ctrl.onInit)){
ctrl.__proto__.onInit = angular.noop;
}
ctrl.onInit();
}
}
}
const ngModule = angular
.module(User.name,User.requiredModules)
.directive(makeSelector(User),userDirectiveFactory);
Root component:
// app.component.ts
import {Component,bootstrap} from 'ng-metadata';
import uiBootstrap from 'angular-ui-bootsrap';
import uiRouter from 'angular-ui-router';
import User from './user/user.component';
import UserList from './user/usersList.component';
import UpperCase from './utils/uppercase.pipe';
@Component({
selector: 'app',
template: `
<div>
<h1>Hello {{ ctrl.greet }} !</h1>
</div>
`,
providers: [uiRouter, uiBootstrap],
directives: [UserList, User],
pipes: [UpperCase]
})
class App{
greet: string = 'World';
}
bootstrap(App);
// should transform to:
import {makeSelector,bootstrap} from 'ng-metadata';
import uiBootstrap from 'angular-ui-bootsrap';
import uiRouter from 'angular-ui-router';
import User from './user/user.component';
import UserList from './user/usersList.component';
import UpperCase from './utils/uppercase.pipe';
class App{
static requiredModules = [
uiRouter, uiBootstrap, UserList, User, UpperCase
];
static selector = 'app';
static template = `
<div>
<h1>Hello {{ ctrl.greet }} !</h1>
</div>
`;
greet: string = 'World';
}
function appDirectiveFactory(){
return {
scope: {},
bindToController: {},
controller: App,
controllerAs: 'ctrl',
template: App.template,
link: (scope,element,attrs,ctrl)=>{
if(angular.isFunction(ctrl.onInit)){
ctrl.__proto__.onInit = angular.noop;
}
ctrl.onInit();
}
}
}
const ngModule = angular
.module(App.name,App.requiredModules)
.directive(makeSelector(App),appDirectiveFactory);
// bootstrap
document.addEventListener('DOMContentLoaded',(e)=>{
angular.bootstrap(App.selector,[App.name]);
});
// user.component.ts
import {Component,Input,Output} from 'ng-metadata';
import {StateConfig} from 'ng-metadata/router';
import FriendsCmp from './friends/friends.component';
interface IUser{
name: string,
age: number,
email: string,
twitter:string
}
@Component({
selector: 'user',
template: `
<div>
<h1>User detail !</h1>
<ul><li>twitter: {{ ctrl.user.twitter }}</li></ul>
<form>
<input ng-model="ctrl.user.name">
<button ng-click="ctrl.changeUser(ctrl.user)">Change</button>
</form>
<ui-view></ui-view>
</div>
`
})
@StateConfig({
name:'user.friends', // child state
path: '/friends', // the same as url in uiRouter
component: FriendsCmp, // instead of template
otherwise: '/' // the same as otherwise via $urlRouteProvider
})
export default class UserCmp{
@Input() user: IUser;
@Output() onChangeUser: (user)=>void;
greet(){
return `${this.user.twitter} says Hello yo!`;
}
changeUser(user){
this.onChangeUser(user);
}
}
// desugars to
export default class UserCmp{
static selector = 'user';
static template = `...`;
static bindings = {
user: '=',
onChangeUSer: '&'
};
static $stateConfig = {
name:'user.friends', // child state
path: '/friends', // the same as url in uiRouter
component: FriendsCmp, // instead of template
otherwise: '/' // the
};
static options = {
// here could be other angular 1 directive apis
// terminal, priority etc...
};
user: IUser;
onChangeUser: (user)=>void;
greet(){
return `${this.user.twitter} says Hello yo!`;
}
changeUser(user){
this.onChangeUser(user);
}
onInit(){}
onDestroy(){}
}
uppercase.pipe.ts
import {Pipe} from 'ng-metadata';
@Pipe({name:'uppercase'})
export default class Uppercase{
transform(input:string){
return angular.isString(input)
? input.toUpperCase()
: input;
}
}
// desugars to
export default class Uppercase{
static name = 'uppercase';
static pure = true;
static $inject = [];
constructor(){
}
transform(input:string){
return angular.isString(input)
? input.toUpperCase()
: input;
}
}
helpMe.service.ts
import {Inject} from 'ng-metadata';
export default class HelpMe{
constructor(@Inject('$log') private $log: ng.ILogService){}
callForHelp(){
this.$log.log('heelp');
}
}
// desugars to:
export default class HelpMe{
static $inject = ['$log'];
constructor(private $log: ng.ILogService){}
callForHelp(){
this.$log.log('heelp');
}
}
user.config.ts
runBlock.$inject = ['$log']
export function runBlock($log: ng.ILogService){
$log.log('run executed!');
}
// in case you need config some providers
configure.$inject = ['$log']
export function configure($logProvider: ng.ILogProviderService){
$logProvider.enable(true);
}
user.ts
import {makeSelector,makeComponent,makePipe,stringify,stateConfig} from 'ng-metadata';
import UserCmp from './user.component';
import Uppercase from './uppercase.pipe';
import HelpMe from './helpMe.service';
import {runBlock,configure} from './user.config'
export default const UserModule = angular
.module('userModule',[])
.run(runBlock)
.config(configure)
.config(registerState(UserCmp))
.directive(makekeSelector(UserCmp),makeComponent(UserCmp))
.service(stringify(HelpMe),HelpMe)
.filter(stringify(Uppercase), makePipe(Uppercase))
.name;
#makePipe:
function makePipe(Type){
const isPure = Type.$inject.length === 0;
filterFactory.$inject = '$injector';
function filterFactory($injector){
const pipeInstance = $injector.instantiate(Type);
return isPure ? pipeInstance.transform : pipeInstance.transform.bind(pipeInstance);
}
return filterFactory;
}
#registerState
function registerStates(Type){
function createHtmlElemString(Type){
return `<${Type.selector}></${${Type.selector}}>`;
}
const states: any[] = Type.$stateConfig;
stateConfig.$inject = ['$stateProvider'];
function stateConfig($stateProvider){
states.forEach((state)=>{
$stateProvider.state(state.name,{
url: state.path,
template: createHtmlElemString(Type)
});
})
}
return stateConfig;
}
This will have one constraints, user won't be able to inject anything, just use locals which are:
It is already available from from within legacy
property on @Component/@Directive
decorator, but we should also support it as a static method on class
@Directive({selector:'[template-changer]'})
class MyDirective{
static compile(tElement: ng.IAugmentedJquery, tAttrs: ng.IAttributes){
// your compile logic
}
}
@Input() foo: string
should generate {bindToController:{foo:'='}}
IE doesn't support name
property for function, this works only for >=ES6.
when this is implemented #39
this is just follow up for developer experience.
We don't wanna force the dev to manually handle callling ngAfter***checked
, we wanna do this automatically, only thing that needs to be done is to inject parent component to this child,
and we will handle those things for him.
methods are Injectable via @Inject
@Component({
selector:'child'
})
class Child implements OnEnter,OnExit{
onEnter(){}
onExit(){}
}
@Component({
selector: 'parent'
})
@StateConfig({
name:'child', url:'/main', component: Child
})
class Parent{}
just pure function which will create correct filter factory function
@Pipe({name:'camelcase'})
class CamelcasePipe{
transform(txt: string, options?): any {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
}
}
// when called
import {makeFilter} from 'ng-metadata';
const filterFactory= makeFilter(CamelcasePipe);
expect(filterFactory).to.be.a('function');
expect( filterFactory('foo-bar') ).to.equal('fooBar'')
just pure function which will create correct directive factory function
@Component({selector:'foo-bar'})
@View({template:`<h1>Hello</h1>`})
Class FooBar{
onInit(){ console.log('hello foo-bar component init'); }
}
// when called
import {makeDirective} from 'ng-metadata';
const directiveFactory = makeDirective(FooBar);
expect(directiveFactory ).to.be.a('function');
expect( directiveFactory() ).to.deep.equal({
scope:{},
bindToController:{},
controller:FooBar,
controllerAs:'vm',
require:['fooBar'],
link: function postLink(scope,element,attrs,controllers,transclude){
const [ownCtrl] = controllers;
ownCtrl.onInit();
},
template:`<h1>Hello</h1>`
})
Not all required controllers are resolved correctly.
Well that's obvious because that behaviour is only supported since angular 1.5 within $onInit
we need to introduce again preLink
and call it from there.
Lessons learned:
It was really a mistake to copy anything from ngForward
:)
just pure function which will create correct directive name from selector string
@Component({selector:'foo-bar'})
@View({template:`<h1>Hello</h1>`})
Class FooBar{
}
// when called
import {makeSelector} from 'ng-metadata';
const directiveName = makeSelector(FooBar);
expect(directiveName).to.equal('fooBar');
this function should be used for registering services/pipes/components/directives within angular container.
This will create name from provided Type as argument.
It has the same name as angular 2 provide
but I didn't find better name for it in ng-metadata
import {MyPipe} from './pipes';
import {UserSvc} from './services';
import {UserCmp} from './components';
import {Notify} from './directives';
import {provide} from 'ng-metadata/ng-metadata';
const ngModule = angular.module('app',[])
.filter(provide(MyPipe),makePipe(MyPipe))
.service(provide(MySvc),MySvc)
.component(provide(UserCmp), makeComponent(UserCmp))
.directive(provide(Notify), makeDirective(Notify));
// so this registers the tokens within angular container
What should be generated:
my
, provide(MyPipe)
will generate my
userSvc
user
, provide(UserCmp)
will generate userCmp
[notify-me]
, provide(Norify)
will generate notifyMe
@Attr() title: string
should generate {bindToController:{title:'@'}}
this shouldn't be used to much because this is not in angular 2.
use @Input
instead.
We need this for legacy code
First off - brilliant project, thanks for all the hard work! I am already successfully using this on a large Angular 1.x app.
Is there any particular reason why optional inputs and outputs are not supported via the decorator style? I.e. The DDO syntax of [..scope/bindToController..]: { myOptionalInput: '=?', myOptionalOuput: '&?' }
If not it would make a great addition!
Many thanks.
when using {legacy: require:'ngModel' }
the implemented method #afterContentInit
doesn't get any params.
refactor DI decorators to cleaner implementation, something like angular 2 does.
Now every parameter decorator is too smart and exposes awkward behaviour ( you cannot write multiple param decorators as you want ).
we need to just register the instance of decorator to some class metadata property and then register proper logic within angular container registration with provide
,makePipe
,makeDirective
and so on.
import {Inject,Host, Component, Output, Input} from 'di';
import {NgModel} from 'directives';
import {OtherService} from 'services';
@Component({
selector:'my-cmp',
template:'hello'
})
export class MyCmp{
@Input() name: string;
@Output() onChange: Function;
constructor(
@Inject('$log') private $log: $log,
@Inject(OtherService) private otherService: OtherService,
@Host() @Inject('ngModel') private ngModel: NgModel
){}
}
expect(MyCmp._mPropMetadata).to.deep.equal({
name: [ InputInstance ],
onChange: [ OutputInstance ]
});
expect(MyCmp._mParameters).to.deep.equal([
[ InjectInstance('$log') ],
[ InjectInstance('otherService') ],
[ InjectInstance('ngModel'), HostInstance ]
]);
expect(MyCmp._mAnnotations).to.deep.equal([
InjectableInstance
]);
and angular container registration:
import * as angular from 'angular';
import {provide, makeDirectvie} from 'di';
import {MyService} from 'myService';
angular.module('myApp',[])
.directive(provide(MyCmp), makeDirective(MyCmp));
expect(MyCmp.$inject).to.deep.equal([
'$log', 'otherService'
// NOTE: there is no ngModel because that's used for directives require
]);
expect(makeDirective(MyCmp)()).to.deep.equal({
scope:{},
bindToController: { name: '=', onChange: '&' },
controllerAs:'ctrl',
controller: MyCmp,
template: 'hello',
require: ['myCmp','ngModel'],
link: function postLink(scope,element,attrs,controllers){
const [ownCtrl, ...requiredCtrls] = controllers;
this.require.slice(1).forEach((ctrlName,idx)=>{
ownCtrl[ ctrlName ] = requiredCtrls[idx];
if(isFunction(ownCtrl.ngAfterContentInit)){
ownCtrl.ngAfterContentInit();
}
});
}
})
provide
will only handle $inject
property, it's the only method which will mutate Type.makePipe
&& makeDirective
will just create factory functions for directive/filter from provided metadata, so no Type mutation@Inject
is not used on constructor it should implicitly create $inject
on that method and add there needed tokens ( this is use case for Provider which has $get
method which needs DI )dependant issues: #33
every project needs docs ๐ ๐
We should create di parameter decorators by leveraging decorator factories
We should create all supported Metadata classes for DI constructor injection, like Angular 2 has:
@Inject
@Injectable
@Host
@Optional
@Self
@SkipSelf
depends on: #33
import {bootstrap} from 'ng-metadata/ng-metadata';
const AppModule = angular.module('app',[]);
boostrap(AppModule)
throws error, document undefined
if component/directive implements OnDestroy interface we should invoke this method on scope.$destroy
from within postLink function.
Please fix this issue, as it is a great feature, it is very useful for documentation purpose. Thanks!
So you don't have to use $scope
never ever again! :)
it should work like angular 2 https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html
@Component({
selector: 'my-cmp',
template: `<p>myProp = {{$ctrl.myProp}}</p>`
})
class MyComponent implements OnChanges {
@Input() myProp: any;
ngOnChanges(changes: {[propName: string]: SimpleChange}) {
console.log('ngOnChanges - myProp = ' + changes['myProp'].currentValue);
}
}
under the hood it should use probably $scope.watchGroup
for perf reasons
There is a lot of mess inside one file - especially directives.ts
We need to separate the logic do small logical module chunks and export only correct public API interface for consumer
we need showcase - classic TODO app will be good ๐ฝ
every project should have cool logo ๐
import {Directive,Inject,Host} from 'ng-metadata/core';
@Directive({selector:'[my-validator]'})
class MyValidatorDirective{}
@Directive({selector:'[my-tester]'})
class MyTesterDirective{
constructor(
@Inject('ngModel') @Host() private ngModel,
@Inject(MyValidatorDirective) @Host() private myValidator
){}
}
creates { require: ['myTester','^ngModel','function MyValidatorDirective'] }
instead of { require: ['myTester','^ngModel','^myValidator'] }
problem is probably in DirectiveResolver
which just returns token
We need to register states for component via @StateConfig similar like ng-forward does
API:
@StateConfig
will provide all options which has uiRouter for $stateParams except those, which can be injectable. Injectable needs to be provided by child component which also makes sense to encapsulate those things together, because they are related.
property | description |
---|---|
name | unique state name |
url | A url fragment with optional parameters. |
component | Reference to Child component which should be activated on this state |
template? | will override component.selector to custom template |
templateUrl? | will override component.selector to custom template |
parent? | uiRouter specific |
views? | uiRouter named views |
abstract? | uiRouter abstractState |
reloadOnSearch? | uiRouter specific |
params? | uiRouter specific |
resolve
:because resolve can be injectable, define static methods on your child component with @Resolve
method decorator.
You shouldn't be using resolve anyway!
so you can end up with
@Component({})
class Child{
@Resolve()
static boo(){}
@Resolve()
static usesDi(@Inject('$http') $http){ return $http.get('/get/me'); }
// and you can get those values from $state
constructor(@Inject('$state') $state) {
// get resolve property
const {boo, usesDi} = $state.$current.locals;
}
}
which will transform to:
Child.resolve = {
boo: function(){},
// this is properly annotated via $inject, behind the scenes
usesDi: function($http){ return $http.get('/get/me'); }
}
and this will be used on parent stateConfig creation
onExit
| onEnter
:because onEnter,onExit can be injectable, implement OnExit
,OnEnter
interface on your child component and use `@Inject if there are any Injectables.
see #28 for more
import { Component, StateConfig, provide, makeDirective } from 'ng-metadata/ng-metadata';
import * as angular from 'angular';
import * as uiRouter from 'ui-router';
@Component({
selector: 'child-a',
template: '{{ ctrl.text }}' // will be 'A resolved!'
})
class ChildA {
text: string;
@Resolve()
static resolveA() {
return 'A resolved!';
}
constructor(@Inject('$state') $state) {
// get resolve property
this.text = $state.$current.locals.resolveA;
}
}
@Component({
selector: 'child-z',
template: 'Im child Z '
})
class ChildZ {}
@Component({
selector: 'parent',
template: `<ui-view></ui-view>`
})
@StateConfig([
{ name: 'childA', url: '/childA', component: ChildA },
{ name: 'childZ', url: '/childZ', component: ChildZ }
])
class Parent {}
angular.module('app',[uiRouter])
.config(provide(Parent))
.directive(provide(Parent), makeDirective(Parent))
.directive(provide(ChildA), makeDirective(ChildA))
.directive(provide(ChildZ), makeDirective(ChildZ));
/src - source files
/test - tests
/dist - directory where build project will be generated
/scripts - helper utils, systemJs bundler scripts etc
/playground - showcase
package.json
.gitignore
Readme.md
karma.conf.js
tsconfig.js
forward_ref
- Allows to refer to references which are not yet defined.
Implement forward_ref
as angular 2 does
https://angular.io/docs/ts/latest/api/core/forwardRef-function.html
there was a breaking change in alpha.47 https://github.com/angular/angular/blob/master/CHANGELOG.md#breaking-changes-1
reflect those changes in codebase
tl;dr
-afterContentInit
+ngAfterContentInit
-onInit
+ngOnInit
-onDestroy
+ngOnDestroy
this will also be BREAKING CHANGE
Project will be powered by SystemJs + npm. So we don't have bundler which is included in jspm.
We need to build our own script which can be moved to separate CLI tool, by leveraging systemjs-builder
this should just encapsulate manual angular bootstrap to mirror kind of angular 2.
usage:
import {bootstrap} from 'ng-metadata';
const ngModule = angular.module('app',[]);
bootstrap(ngModule);
is just replacement for:
const ngModule = angular.module('app',[]);
angular.element( document ).ready( ()=> {
angular.bootstrap( document, [ ngModule.name ], {
strictDi: true
} )
} );
we need something like this for @Directive
in controller instantiation
we support just Magic strings for Inject
import {Inject} from 'ng-metadata/ng-metadata';
class MySvc{}
class FooSvc{
constructor(@Inject('mySvc', mySvc))
}
It would be nice to get rid of them and use references like in Angular 2
import {Inject} from 'ng-metadata/ng-metadata';
class MySvc{}
class FooSvc{
constructor(@Inject(MySvc, mySvc))
}
@Output() onFoo: Function
should generate {bindToController:{onFoo:'&'}}
depends on #1
We should create generic factory methods for decorators creation like Angular 2 folks do
https://jsbin.com/kapecek/16/edit?html,js,output
ngChildrenChanged()
from controller which is parent of those children. Also user needs to call this method within $scope.$on('$destroy', callback)
to properly clean up GC.https://angular.io/docs/ts/latest/api/core/ViewChildren-var.html
https://angular.io/docs/ts/latest/api/core/ViewChild-var.html
https://angular.io/docs/ts/latest/api/core/ContentChildren-var.html
https://angular.io/docs/ts/latest/api/core/ContentChild-var.html
Currently we support DI only with @Inject
parameter decorators which is not very convenient.
We need to allow same UX for DI as angular 2 via typescript annotations
reflect-metadata
library@Inject
via design:paramtypes
under the hood@Injectable()
class MyService{
constructor(@Inject(Logger) private logger: Logger){}
}
@Injectable()
class MyService{
constructor(private logger: Logger){}
}
Use one way data binding only and by default as angular 1.5 uses
angular/angular.js@4ac23c0#diff-a732922b631efed1b9f33a24082ae0dbL3037
angular doesn't copies those parent values, so if we have object and modify a property it will be mutated on parent, which is not very convenient. We wanna push best practices and stay immutable, so we will copy @Input
props via angular.copy
.
Although we should keep fallback for people which really need 2 way binding:
// one way only
@Component({
selector: 'hero',
template:`hello`
})
class HeroComponent{
@Input() name: string;
@Input() skills: string[];
}
// two-way fallback via alias only
@Component({
selector: 'hero',
template:`hello`
})
class HeroComponent{
@Input('=') name: string;
@Input('=') skills: string[];
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.